1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.animation.Animator;
20 import android.animation.Animator.AnimatorListener;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.animation.LayoutTransition;
24 import android.animation.ObjectAnimator;
25 import android.animation.PropertyValuesHolder;
26 import android.animation.TimeInterpolator;
27 import android.animation.ValueAnimator;
28 import android.animation.ValueAnimator.AnimatorUpdateListener;
29 import android.app.WallpaperManager;
30 import android.appwidget.AppWidgetHostView;
31 import android.appwidget.AppWidgetProviderInfo;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.SharedPreferences;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.res.Resources;
39 import android.content.res.TypedArray;
40 import android.graphics.Bitmap;
41 import android.graphics.Canvas;
42 import android.graphics.Matrix;
43 import android.graphics.Paint;
44 import android.graphics.Point;
45 import android.graphics.PointF;
46 import android.graphics.Rect;
47 import android.graphics.Region.Op;
48 import android.graphics.drawable.Drawable;
49 import android.net.Uri;
50 import android.os.AsyncTask;
51 import android.os.Handler;
52 import android.os.IBinder;
53 import android.os.Parcelable;
54 import android.support.v4.view.ViewCompat;
55 import android.util.AttributeSet;
56 import android.util.Log;
57 import android.util.SparseArray;
58 import android.view.Choreographer;
59 import android.view.Display;
60 import android.view.MotionEvent;
61 import android.view.View;
62 import android.view.ViewGroup;
63 import android.view.accessibility.AccessibilityManager;
64 import android.view.animation.DecelerateInterpolator;
65 import android.view.animation.Interpolator;
66 import android.widget.TextView;
67
68 import com.android.launcher3.FolderIcon.FolderRingAnimator;
69 import com.android.launcher3.Launcher.CustomContentCallbacks;
70 import com.android.launcher3.LauncherSettings.Favorites;
71 import com.android.launcher3.compat.PackageInstallerCompat;
72 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
73 import com.android.launcher3.compat.UserHandleCompat;
74
75 import java.util.ArrayList;
76 import java.util.HashMap;
77 import java.util.HashSet;
78 import java.util.Iterator;
79 import java.util.Map;
80 import java.util.Set;
81 import java.util.concurrent.atomic.AtomicInteger;
82
83 /**
84 * The workspace is a wide area with a wallpaper and a finite number of pages.
85 * Each page contains a number of icons, folders or widgets the user can
86 * interact with. A workspace is meant to be used with a fixed width only.
87 */
88 public class Workspace extends SmoothPagedView
89 implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
90 DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
91 Insettable {
92 private static final String TAG = "Launcher.Workspace";
93
94 // Y rotation to apply to the workspace screens
95 private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
96
97 private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
98 private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
99 private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
100
101 protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
102 protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
103
104 private static final int BACKGROUND_FADE_OUT_DURATION = 350;
105 private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
106 private static final int FLING_THRESHOLD_VELOCITY = 500;
107
108 private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
109
110 static final boolean MAP_NO_RECURSE = false;
111 static final boolean MAP_RECURSE = true;
112
113 // These animators are used to fade the children's outlines
114 private ObjectAnimator mChildrenOutlineFadeInAnimation;
115 private ObjectAnimator mChildrenOutlineFadeOutAnimation;
116 private float mChildrenOutlineAlpha = 0;
117
118 // These properties refer to the background protection gradient used for AllApps and Customize
119 private ValueAnimator mBackgroundFadeInAnimation;
120 private ValueAnimator mBackgroundFadeOutAnimation;
121
122 private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
123 private long mTouchDownTime = -1;
124 private long mCustomContentShowTime = -1;
125
126 private LayoutTransition mLayoutTransition;
127 private final WallpaperManager mWallpaperManager;
128 private IBinder mWindowToken;
129
130 private int mOriginalDefaultPage;
131 private int mDefaultPage;
132
133 private ShortcutAndWidgetContainer mDragSourceInternal;
134 private static boolean sAccessibilityEnabled;
135
136 // The screen id used for the empty screen always present to the right.
137 final static long EXTRA_EMPTY_SCREEN_ID = -201;
138 private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
139
140 private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
141 private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
142
143 private Runnable mRemoveEmptyScreenRunnable;
144 private boolean mDeferRemoveExtraEmptyScreen = false;
145
146 /**
147 * CellInfo for the cell that is currently being dragged
148 */
149 private CellLayout.CellInfo mDragInfo;
150
151 /**
152 * Target drop area calculated during last acceptDrop call.
153 */
154 private int[] mTargetCell = new int[2];
155 private int mDragOverX = -1;
156 private int mDragOverY = -1;
157
158 static Rect mLandscapeCellLayoutMetrics = null;
159 static Rect mPortraitCellLayoutMetrics = null;
160
161 CustomContentCallbacks mCustomContentCallbacks;
162 boolean mCustomContentShowing;
163 private float mLastCustomContentScrollProgress = -1f;
164 private String mCustomContentDescription = "";
165
166 /**
167 * The CellLayout that is currently being dragged over
168 */
169 private CellLayout mDragTargetLayout = null;
170 /**
171 * The CellLayout that we will show as glowing
172 */
173 private CellLayout mDragOverlappingLayout = null;
174
175 /**
176 * The CellLayout which will be dropped to
177 */
178 private CellLayout mDropToLayout = null;
179
180 private Launcher mLauncher;
181 private IconCache mIconCache;
182 private DragController mDragController;
183
184 // These are temporary variables to prevent having to allocate a new object just to
185 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
186 private int[] mTempCell = new int[2];
187 private int[] mTempPt = new int[2];
188 private int[] mTempEstimate = new int[2];
189 private float[] mDragViewVisualCenter = new float[2];
190 private float[] mTempCellLayoutCenterCoordinates = new float[2];
191 private Matrix mTempInverseMatrix = new Matrix();
192
193 private SpringLoadedDragController mSpringLoadedDragController;
194 private float mSpringLoadedShrinkFactor;
195 private float mOverviewModeShrinkFactor;
196
197 // State variable that indicates whether the pages are small (ie when you're
198 // in all apps or customize mode)
199
200 enum State { NORMAL, NORMAL_HIDDEN, SPRING_LOADED, OVERVIEW, OVERVIEW_HIDDEN};
201 private State mState = State.NORMAL;
202 private boolean mIsSwitchingState = false;
203
204 boolean mAnimatingViewIntoPlace = false;
205 boolean mIsDragOccuring = false;
206 boolean mChildrenLayersEnabled = true;
207
208 private boolean mStripScreensOnPageStopMoving = false;
209
210 /** Is the user is dragging an item near the edge of a page? */
211 private boolean mInScrollArea = false;
212
213 private HolographicOutlineHelper mOutlineHelper;
214 private Bitmap mDragOutline = null;
215 private static final Rect sTempRect = new Rect();
216 private final int[] mTempXY = new int[2];
217 private int[] mTempVisiblePagesRange = new int[2];
218 private boolean mOverscrollEffectSet;
219 public static final int DRAG_BITMAP_PADDING = 2;
220 private boolean mWorkspaceFadeInAdjacentScreens;
221
222 WallpaperOffsetInterpolator mWallpaperOffset;
223 private boolean mWallpaperIsLiveWallpaper;
224 private int mNumPagesForWallpaperParallax;
225 private float mLastSetWallpaperOffsetSteps = 0;
226
227 private Runnable mDelayedResizeRunnable;
228 private Runnable mDelayedSnapToPageRunnable;
229 private Point mDisplaySize = new Point();
230 private int mCameraDistance;
231
232 // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
233 private static final int FOLDER_CREATION_TIMEOUT = 0;
234 public static final int REORDER_TIMEOUT = 350;
235 private final Alarm mFolderCreationAlarm = new Alarm();
236 private final Alarm mReorderAlarm = new Alarm();
237 private FolderRingAnimator mDragFolderRingAnimator = null;
238 private FolderIcon mDragOverFolderIcon = null;
239 private boolean mCreateUserFolderOnDrop = false;
240 private boolean mAddToExistingFolderOnDrop = false;
241 private DropTarget.DragEnforcer mDragEnforcer;
242 private float mMaxDistanceForFolderCreation;
243
244 private final Canvas mCanvas = new Canvas();
245
246 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
247 private float mXDown;
248 private float mYDown;
249 final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
250 final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
251 final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
252
253 // Relating to the animation of items being dropped externally
254 public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
255 public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
256 public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
257 public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
258 public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
259
260 // Related to dragging, folder creation and reordering
261 private static final int DRAG_MODE_NONE = 0;
262 private static final int DRAG_MODE_CREATE_FOLDER = 1;
263 private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
264 private static final int DRAG_MODE_REORDER = 3;
265 private int mDragMode = DRAG_MODE_NONE;
266 private int mLastReorderX = -1;
267 private int mLastReorderY = -1;
268
269 private SparseArray<Parcelable> mSavedStates;
270 private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
271
272 // These variables are used for storing the initial and final values during workspace animations
273 private int mSavedScrollX;
274 private float mSavedRotationY;
275 private float mSavedTranslationX;
276
277 private float mCurrentScale;
278 private float mNewScale;
279 private float[] mOldBackgroundAlphas;
280 private float[] mOldAlphas;
281 private float[] mNewBackgroundAlphas;
282 private float[] mNewAlphas;
283 private int mLastChildCount = -1;
284 private float mTransitionProgress;
285
286 float mOverScrollEffect = 0f;
287
288 private Runnable mDeferredAction;
289 private boolean mDeferDropAfterUninstall;
290 private boolean mUninstallSuccessful;
291
292 private final Runnable mBindPages = new Runnable() {
293 @Override
294 public void run() {
295 mLauncher.getModel().bindRemainingSynchronousPages();
296 }
297 };
298
299 /**
300 * Used to inflate the Workspace from XML.
301 *
302 * @param context The application's context.
303 * @param attrs The attributes set containing the Workspace's customization values.
304 */
305 public Workspace(Context context, AttributeSet attrs) {
306 this(context, attrs, 0);
307 }
308
309 /**
310 * Used to inflate the Workspace from XML.
311 *
312 * @param context The application's context.
313 * @param attrs The attributes set containing the Workspace's customization values.
314 * @param defStyle Unused.
315 */
316 public Workspace(Context context, AttributeSet attrs, int defStyle) {
317 super(context, attrs, defStyle);
318 mContentIsRefreshable = false;
319
320 mOutlineHelper = HolographicOutlineHelper.obtain(context);
321
322 mDragEnforcer = new DropTarget.DragEnforcer(context);
323 // With workspace, data is available straight from the get-go
324 setDataIsReady();
325
326 mLauncher = (Launcher) context;
327 final Resources res = getResources();
328 mWorkspaceFadeInAdjacentScreens = LauncherAppState.getInstance().getDynamicGrid().
329 getDeviceProfile().shouldFadeAdjacentWorkspaceScreens();
330 mFadeInAdjacentScreens = false;
331 mWallpaperManager = WallpaperManager.getInstance(context);
332
333 LauncherAppState app = LauncherAppState.getInstance();
334 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
335 TypedArray a = context.obtainStyledAttributes(attrs,
336 R.styleable.Workspace, defStyle, 0);
337 mSpringLoadedShrinkFactor =
338 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
339 mOverviewModeShrinkFactor = grid.getOverviewModeScale();
340 mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
341 mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
342 a.recycle();
343
344 setOnHierarchyChangeListener(this);
345 setHapticFeedbackEnabled(false);
346
347 initWorkspace();
348
349 // Disable multitouch across the workspace/all apps/customize tray
350 setMotionEventSplittingEnabled(true);
351 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
352 }
353
354 @Override
355 public void setInsets(Rect insets) {
356 mInsets.set(insets);
357
358 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
359 if (customScreen != null) {
360 View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0);
361 if (customContent instanceof Insettable) {
362 ((Insettable) customContent).setInsets(mInsets);
363 }
364 }
365 }
366
367 // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
368 // dimension if unsuccessful
369 public int[] estimateItemSize(int hSpan, int vSpan,
370 ItemInfo itemInfo, boolean springLoaded) {
371 int[] size = new int[2];
372 if (getChildCount() > 0) {
373 // Use the first non-custom page to estimate the child position
374 CellLayout cl = (CellLayout) getChildAt(numCustomPages());
375 Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
376 size[0] = r.width();
377 size[1] = r.height();
378 if (springLoaded) {
379 size[0] *= mSpringLoadedShrinkFactor;
380 size[1] *= mSpringLoadedShrinkFactor;
381 }
382 return size;
383 } else {
384 size[0] = Integer.MAX_VALUE;
385 size[1] = Integer.MAX_VALUE;
386 return size;
387 }
388 }
389
390 public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
391 int hCell, int vCell, int hSpan, int vSpan) {
392 Rect r = new Rect();
393 cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
394 return r;
395 }
396
397 public void onDragStart(final DragSource source, Object info, int dragAction) {
398 mIsDragOccuring = true;
399 updateChildrenLayersEnabled(false);
400 mLauncher.lockScreenOrientation();
401 mLauncher.onInteractionBegin();
402 setChildrenBackgroundAlphaMultipliers(1f);
403 // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
404 InstallShortcutReceiver.enableInstallQueue();
405 UninstallShortcutReceiver.enableUninstallQueue();
406 post(new Runnable() {
407 @Override
408 public void run() {
409 if (mIsDragOccuring) {
410 mDeferRemoveExtraEmptyScreen = false;
411 addExtraEmptyScreenOnDrag();
412 }
413 }
414 });
415 }
416
417
418 public void deferRemoveExtraEmptyScreen() {
419 mDeferRemoveExtraEmptyScreen = true;
420 }
421
422 public void onDragEnd() {
423 if (!mDeferRemoveExtraEmptyScreen) {
424 removeExtraEmptyScreen(true, mDragSourceInternal != null);
425 }
426
427 mIsDragOccuring = false;
428 updateChildrenLayersEnabled(false);
429 mLauncher.unlockScreenOrientation(false);
430
431 // Re-enable any Un/InstallShortcutReceiver and now process any queued items
432 InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
433 UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
434
435 mDragSourceInternal = null;
436 mLauncher.onInteractionEnd();
437 }
438
439 /**
440 * Initializes various states for this workspace.
441 */
442 protected void initWorkspace() {
443 mCurrentPage = mDefaultPage;
444 Launcher.setScreen(mCurrentPage);
445 LauncherAppState app = LauncherAppState.getInstance();
446 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
447 mIconCache = app.getIconCache();
448 setWillNotDraw(false);
449 setClipChildren(false);
450 setClipToPadding(false);
451 setChildrenDrawnWithCacheEnabled(true);
452
453 setMinScale(mOverviewModeShrinkFactor);
454 setupLayoutTransition();
455
456 mWallpaperOffset = new WallpaperOffsetInterpolator();
457 Display display = mLauncher.getWindowManager().getDefaultDisplay();
458 display.getSize(mDisplaySize);
459
460 mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
461 mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
462
463 // Set the wallpaper dimensions when Launcher starts up
464 setWallpaperDimension();
465 }
466
467 private void setupLayoutTransition() {
468 // We want to show layout transitions when pages are deleted, to close the gap.
469 mLayoutTransition = new LayoutTransition();
470 mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
471 mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
472 mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
473 mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
474 setLayoutTransition(mLayoutTransition);
475 }
476
477 void enableLayoutTransitions() {
478 setLayoutTransition(mLayoutTransition);
479 }
480 void disableLayoutTransitions() {
481 setLayoutTransition(null);
482 }
483
484 @Override
485 protected int getScrollMode() {
486 return SmoothPagedView.X_LARGE_MODE;
487 }
488
489 @Override
490 public void onChildViewAdded(View parent, View child) {
491 if (!(child instanceof CellLayout)) {
492 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
493 }
494 CellLayout cl = ((CellLayout) child);
495 cl.setOnInterceptTouchListener(this);
496 cl.setClickable(true);
497 cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
498 super.onChildViewAdded(parent, child);
499 }
500
501 protected boolean shouldDrawChild(View child) {
502 final CellLayout cl = (CellLayout) child;
503 return super.shouldDrawChild(child) &&
504 (mIsSwitchingState ||
505 cl.getShortcutsAndWidgets().getAlpha() > 0 ||
506 cl.getBackgroundAlpha() > 0);
507 }
508
509 /**
510 * @return The open folder on the current screen, or null if there is none
511 */
512 Folder getOpenFolder() {
513 DragLayer dragLayer = mLauncher.getDragLayer();
514 int count = dragLayer.getChildCount();
515 for (int i = 0; i < count; i++) {
516 View child = dragLayer.getChildAt(i);
517 if (child instanceof Folder) {
518 Folder folder = (Folder) child;
519 if (folder.getInfo().opened)
520 return folder;
521 }
522 }
523 return null;
524 }
525
526 boolean isTouchActive() {
527 return mTouchState != TOUCH_STATE_REST;
528 }
529
530 public void removeAllWorkspaceScreens() {
531 // Disable all layout transitions before removing all pages to ensure that we don't get the
532 // transition animations competing with us changing the scroll when we add pages or the
533 // custom content screen
534 disableLayoutTransitions();
535
536 // Since we increment the current page when we call addCustomContentPage via bindScreens
537 // (and other places), we need to adjust the current page back when we clear the pages
538 if (hasCustomContent()) {
539 removeCustomContentPage();
540 }
541
542 // Remove the pages and clear the screen models
543 removeAllViews();
544 mScreenOrder.clear();
545 mWorkspaceScreens.clear();
546
547 // Re-enable the layout transitions
548 enableLayoutTransitions();
549 }
550
551 public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
552 // Find the index to insert this view into. If the empty screen exists, then
553 // insert it before that.
554 int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
555 if (insertIndex < 0) {
556 insertIndex = mScreenOrder.size();
557 }
558 return insertNewWorkspaceScreen(screenId, insertIndex);
559 }
560
561 public long insertNewWorkspaceScreen(long screenId) {
562 return insertNewWorkspaceScreen(screenId, getChildCount());
563 }
564
565 public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
566 // Log to disk
567 Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId +
568 " at index: " + insertIndex, true);
569
570 if (mWorkspaceScreens.containsKey(screenId)) {
571 throw new RuntimeException("Screen id " + screenId + " already exists!");
572 }
573
574 CellLayout newScreen = (CellLayout)
575 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
576
577 newScreen.setOnLongClickListener(mLongClickListener);
578 newScreen.setOnClickListener(mLauncher);
579 newScreen.setSoundEffectsEnabled(false);
580 mWorkspaceScreens.put(screenId, newScreen);
581 mScreenOrder.add(insertIndex, screenId);
582 addView(newScreen, insertIndex);
583 return screenId;
584 }
585
586 public void createCustomContentContainer() {
587 CellLayout customScreen = (CellLayout)
588 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
589 customScreen.disableBackground();
590 customScreen.disableDragTarget();
591
592 mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
593 mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
594
595 // We want no padding on the custom content
596 customScreen.setPadding(0, 0, 0, 0);
597
598 addFullScreenPage(customScreen);
599
600 // Ensure that the current page and default page are maintained.
601 mDefaultPage = mOriginalDefaultPage + 1;
602
603 // Update the custom content hint
604 if (mRestorePage != INVALID_RESTORE_PAGE) {
605 mRestorePage = mRestorePage + 1;
606 } else {
607 setCurrentPage(getCurrentPage() + 1);
608 }
609 }
610
611 public void removeCustomContentPage() {
612 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
613 if (customScreen == null) {
614 throw new RuntimeException("Expected custom content screen to exist");
615 }
616
617 mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID);
618 mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID);
619 removeView(customScreen);
620
621 if (mCustomContentCallbacks != null) {
622 mCustomContentCallbacks.onScrollProgressChanged(0);
623 mCustomContentCallbacks.onHide();
624 }
625
626 mCustomContentCallbacks = null;
627
628 // Ensure that the current page and default page are maintained.
629 mDefaultPage = mOriginalDefaultPage - 1;
630
631 // Update the custom content hint
632 if (mRestorePage != INVALID_RESTORE_PAGE) {
633 mRestorePage = mRestorePage - 1;
634 } else {
635 setCurrentPage(getCurrentPage() - 1);
636 }
637 }
638
639 public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
640 String description) {
641 if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
642 throw new RuntimeException("Expected custom content screen to exist");
643 }
644
645 // Add the custom content to the full screen custom page
646 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
647 int spanX = customScreen.getCountX();
648 int spanY = customScreen.getCountY();
649 CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
650 lp.canReorder = false;
651 lp.isFullscreen = true;
652 if (customContent instanceof Insettable) {
653 ((Insettable)customContent).setInsets(mInsets);
654 }
655
656 // Verify that the child is removed from any existing parent.
657 if (customContent.getParent() instanceof ViewGroup) {
658 ViewGroup parent = (ViewGroup) customContent.getParent();
659 parent.removeView(customContent);
660 }
661 customScreen.removeAllViews();
662 customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
663 mCustomContentDescription = description;
664
665 mCustomContentCallbacks = callbacks;
666 }
667
668 public void addExtraEmptyScreenOnDrag() {
669 // Log to disk
670 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreenOnDrag()", true);
671
672 boolean lastChildOnScreen = false;
673 boolean childOnFinalScreen = false;
674
675 // Cancel any pending removal of empty screen
676 mRemoveEmptyScreenRunnable = null;
677
678 if (mDragSourceInternal != null) {
679 if (mDragSourceInternal.getChildCount() == 1) {
680 lastChildOnScreen = true;
681 }
682 CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
683 if (indexOfChild(cl) == getChildCount() - 1) {
684 childOnFinalScreen = true;
685 }
686 }
687
688 // If this is the last item on the final screen
689 if (lastChildOnScreen && childOnFinalScreen) {
690 return;
691 }
692 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
693 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
694 }
695 }
696
697 public boolean addExtraEmptyScreen() {
698 // Log to disk
699 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreen()", true);
700
701 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
702 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
703 return true;
704 }
705 return false;
706 }
707
708 private void convertFinalScreenToEmptyScreenIfNecessary() {
709 // Log to disk
710 Launcher.addDumpLog(TAG, "11683562 - convertFinalScreenToEmptyScreenIfNecessary()", true);
711
712 if (mLauncher.isWorkspaceLoading()) {
713 // Invalid and dangerous operation if workspace is loading
714 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
715 return;
716 }
717
718 if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
719 long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
720
721 if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) return;
722 CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
723
724 // If the final screen is empty, convert it to the extra empty screen
725 if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 &&
726 !finalScreen.isDropPending()) {
727 mWorkspaceScreens.remove(finalScreenId);
728 mScreenOrder.remove(finalScreenId);
729
730 // if this is the last non-custom content screen, convert it to the empty screen
731 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
732 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
733
734 // Update the model if we have changed any screens
735 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
736 Launcher.addDumpLog(TAG, "11683562 - extra empty screen: " + finalScreenId, true);
737 }
738 }
739
740 public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
741 removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
742 }
743
744 public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
745 final int delay, final boolean stripEmptyScreens) {
746 // Log to disk
747 Launcher.addDumpLog(TAG, "11683562 - removeExtraEmptyScreen()", true);
748 if (mLauncher.isWorkspaceLoading()) {
749 // Don't strip empty screens if the workspace is still loading
750 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
751 return;
752 }
753
754 if (delay > 0) {
755 postDelayed(new Runnable() {
756 @Override
757 public void run() {
758 removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
759 }
760 }, delay);
761 return;
762 }
763
764 convertFinalScreenToEmptyScreenIfNecessary();
765 if (hasExtraEmptyScreen()) {
766 int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
767 if (getNextPage() == emptyIndex) {
768 snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
769 fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
770 onComplete, stripEmptyScreens);
771 } else {
772 fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
773 onComplete, stripEmptyScreens);
774 }
775 return;
776 } else if (stripEmptyScreens) {
777 // If we're not going to strip the empty screens after removing
778 // the extra empty screen, do it right away.
779 stripEmptyScreens();
780 }
781
782 if (onComplete != null) {
783 onComplete.run();
784 }
785 }
786
787 private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
788 final boolean stripEmptyScreens) {
789 // Log to disk
790 // XXX: Do we need to update LM workspace screens below?
791 Launcher.addDumpLog(TAG, "11683562 - fadeAndRemoveEmptyScreen()", true);
792 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
793 PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
794
795 final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
796
797 mRemoveEmptyScreenRunnable = new Runnable() {
798 @Override
799 public void run() {
800 if (hasExtraEmptyScreen()) {
801 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
802 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
803 removeView(cl);
804 if (stripEmptyScreens) {
805 stripEmptyScreens();
806 }
807 }
808 }
809 };
810
811 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
812 oa.setDuration(duration);
813 oa.setStartDelay(delay);
814 oa.addListener(new AnimatorListenerAdapter() {
815 @Override
816 public void onAnimationEnd(Animator animation) {
817 if (mRemoveEmptyScreenRunnable != null) {
818 mRemoveEmptyScreenRunnable.run();
819 }
820 if (onComplete != null) {
821 onComplete.run();
822 }
823 }
824 });
825 oa.start();
826 }
827
828 public boolean hasExtraEmptyScreen() {
829 int nScreens = getChildCount();
830 nScreens = nScreens - numCustomPages();
831 return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
832 }
833
834 public long commitExtraEmptyScreen() {
835 // Log to disk
836 Launcher.addDumpLog(TAG, "11683562 - commitExtraEmptyScreen()", true);
837 if (mLauncher.isWorkspaceLoading()) {
838 // Invalid and dangerous operation if workspace is loading
839 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
840 return -1;
841 }
842
843 int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
844 CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
845 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
846 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
847
848 long newId = LauncherAppState.getLauncherProvider().generateNewScreenId();
849 mWorkspaceScreens.put(newId, cl);
850 mScreenOrder.add(newId);
851
852 // Update the page indicator marker
853 if (getPageIndicator() != null) {
854 getPageIndicator().updateMarker(index, getPageIndicatorMarker(index));
855 }
856
857 // Update the model for the new screen
858 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
859
860 return newId;
861 }
862
863 public CellLayout getScreenWithId(long screenId) {
864 CellLayout layout = mWorkspaceScreens.get(screenId);
865 return layout;
866 }
867
868 public long getIdForScreen(CellLayout layout) {
869 Iterator<Long> iter = mWorkspaceScreens.keySet().iterator();
870 while (iter.hasNext()) {
871 long id = iter.next();
872 if (mWorkspaceScreens.get(id) == layout) {
873 return id;
874 }
875 }
876 return -1;
877 }
878
879 public int getPageIndexForScreenId(long screenId) {
880 return indexOfChild(mWorkspaceScreens.get(screenId));
881 }
882
883 public long getScreenIdForPageIndex(int index) {
884 if (0 <= index && index < mScreenOrder.size()) {
885 return mScreenOrder.get(index);
886 }
887 return -1;
888 }
889
890 ArrayList<Long> getScreenOrder() {
891 return mScreenOrder;
892 }
893
894 public void stripEmptyScreens() {
895 // Log to disk
896 Launcher.addDumpLog(TAG, "11683562 - stripEmptyScreens()", true);
897
898 if (mLauncher.isWorkspaceLoading()) {
899 // Don't strip empty screens if the workspace is still loading.
900 // This is dangerous and can result in data loss.
901 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
902 return;
903 }
904
905 if (isPageMoving()) {
906 mStripScreensOnPageStopMoving = true;
907 return;
908 }
909
910 int currentPage = getNextPage();
911 ArrayList<Long> removeScreens = new ArrayList<Long>();
912 for (Long id: mWorkspaceScreens.keySet()) {
913 CellLayout cl = mWorkspaceScreens.get(id);
914 if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
915 removeScreens.add(id);
916 }
917 }
918
919 // We enforce at least one page to add new items to. In the case that we remove the last
920 // such screen, we convert the last screen to the empty screen
921 int minScreens = 1 + numCustomPages();
922
923 int pageShift = 0;
924 for (Long id: removeScreens) {
925 Launcher.addDumpLog(TAG, "11683562 - removing id: " + id, true);
926 CellLayout cl = mWorkspaceScreens.get(id);
927 mWorkspaceScreens.remove(id);
928 mScreenOrder.remove(id);
929
930 if (getChildCount() > minScreens) {
931 if (indexOfChild(cl) < currentPage) {
932 pageShift++;
933 }
934 removeView(cl);
935 } else {
936 // if this is the last non-custom content screen, convert it to the empty screen
937 mRemoveEmptyScreenRunnable = null;
938 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
939 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
940 }
941 }
942
943 if (!removeScreens.isEmpty()) {
944 // Update the model if we have changed any screens
945 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
946 }
947
948 if (pageShift >= 0) {
949 setCurrentPage(currentPage - pageShift);
950 }
951 }
952
953 // See implementation for parameter definition.
954 void addInScreen(View child, long container, long screenId,
955 int x, int y, int spanX, int spanY) {
956 addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
957 }
958
959 // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
960 // See implementation for parameter definition.
961 void addInScreenFromBind(View child, long container, long screenId, int x, int y,
962 int spanX, int spanY) {
963 addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
964 }
965
966 // See implementation for parameter definition.
967 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
968 boolean insert) {
969 addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
970 }
971
972 /**
973 * Adds the specified child in the specified screen. The position and dimension of
974 * the child are defined by x, y, spanX and spanY.
975 *
976 * @param child The child to add in one of the workspace's screens.
977 * @param screenId The screen in which to add the child.
978 * @param x The X position of the child in the screen's grid.
979 * @param y The Y position of the child in the screen's grid.
980 * @param spanX The number of cells spanned horizontally by the child.
981 * @param spanY The number of cells spanned vertically by the child.
982 * @param insert When true, the child is inserted at the beginning of the children list.
983 * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
984 * the x and y position in which to place hotseat items. Otherwise
985 * we use the x and y position to compute the rank.
986 */
987 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
988 boolean insert, boolean computeXYFromRank) {
989 if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
990 if (getScreenWithId(screenId) == null) {
991 Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
992 // DEBUGGING - Print out the stack trace to see where we are adding from
993 new Throwable().printStackTrace();
994 return;
995 }
996 }
997 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
998 // This should never happen
999 throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
1000 }
1001
1002 final CellLayout layout;
1003 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1004 layout = mLauncher.getHotseat().getLayout();
1005 child.setOnKeyListener(new HotseatIconKeyEventListener());
1006
1007 // Hide folder title in the hotseat
1008 if (child instanceof FolderIcon) {
1009 ((FolderIcon) child).setTextVisible(false);
1010 }
1011
1012 if (computeXYFromRank) {
1013 x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
1014 y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
1015 } else {
1016 screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
1017 }
1018 } else {
1019 // Show folder title if not in the hotseat
1020 if (child instanceof FolderIcon) {
1021 ((FolderIcon) child).setTextVisible(true);
1022 }
1023 layout = getScreenWithId(screenId);
1024 child.setOnKeyListener(new IconKeyEventListener());
1025 }
1026
1027 ViewGroup.LayoutParams genericLp = child.getLayoutParams();
1028 CellLayout.LayoutParams lp;
1029 if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
1030 lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
1031 } else {
1032 lp = (CellLayout.LayoutParams) genericLp;
1033 lp.cellX = x;
1034 lp.cellY = y;
1035 lp.cellHSpan = spanX;
1036 lp.cellVSpan = spanY;
1037 }
1038
1039 if (spanX < 0 && spanY < 0) {
1040 lp.isLockedToGrid = false;
1041 }
1042
1043 // Get the canonical child id to uniquely represent this view in this screen
1044 ItemInfo info = (ItemInfo) child.getTag();
1045 int childId = mLauncher.getViewIdForItem(info);
1046
1047 boolean markCellsAsOccupied = !(child instanceof Folder);
1048 if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
1049 // TODO: This branch occurs when the workspace is adding views
1050 // outside of the defined grid
1051 // maybe we should be deleting these items from the LauncherModel?
1052 Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to Cel🔵
1053 }
1054
1055 if (!(child instanceof Folder)) {
1056 child.setHapticFeedbackEnabled(false);
1057 child.setOnLongClickListener(mLongClickListener);
1058 }
1059 if (child instanceof DropTarget) {
1060 mDragController.addDropTarget((DropTarget) child);
1061 }
1062 }
1063
1064 /**
1065 * Called directly from a CellLayout (not by the framework), after we've been added as a
1066 * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
1067 * that it should intercept touch events, which is not something that is normally supported.
1068 */
1069 @Override
1070 public boolean onTouch(View v, MotionEvent event) {
1071 return (workspaceInModalState() || !isFinishedSwitchingState())
1072 || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
1073 }
1074
1075 public boolean isSwitchingState() {
1076 return mIsSwitchingState;
1077 }
1078
1079 /** This differs from isSwitchingState in that we take into account how far the transition
1080 * has completed. */
1081 public boolean isFinishedSwitchingState() {
1082 return !mIsSwitchingState || (mTransitionProgress > 0.5f);
1083 }
1084
1085 protected void onWindowVisibilityChanged (int visibility) {
1086 mLauncher.onWindowVisibilityChanged(visibility);
1087 }
1088
1089 @Override
1090 public boolean dispatchUnhandledMove(View focused, int direction) {
1091 if (workspaceInModalState() || !isFinishedSwitchingState()) {
1092 // when the home screens are shrunken, shouldn't allow side-scrolling
1093 return false;
1094 }
1095 return super.dispatchUnhandledMove(focused, direction);
1096 }
1097
1098 @Override
1099 public boolean onInterceptTouchEvent(MotionEvent ev) {
1100 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1101 case MotionEvent.ACTION_DOWN:
1102 mXDown = ev.getX();
1103 mYDown = ev.getY();
1104 mTouchDownTime = System.currentTimeMillis();
1105 break;
1106 case MotionEvent.ACTION_POINTER_UP:
1107 case MotionEvent.ACTION_UP:
1108 if (mTouchState == TOUCH_STATE_REST) {
1109 final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
1110 if (currentPage != null && !currentPage.lastDownOnOccupiedCell()) {
1111 onWallpaperTap(ev);
1112 }
1113 }
1114 }
1115 return super.onInterceptTouchEvent(ev);
1116 }
1117
1118 @Override
1119 public boolean onGenericMotionEvent(MotionEvent event) {
1120 // Ignore pointer scroll events if the custom content doesn't allow scrolling.
1121 if ((getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID)
1122 && (mCustomContentCallbacks != null)
1123 && !mCustomContentCallbacks.isScrollingAllowed()) {
1124 return false;
1125 }
1126 return super.onGenericMotionEvent(event);
1127 }
1128
1129 protected void reinflateWidgetsIfNecessary() {
1130 final int clCount = getChildCount();
1131 for (int i = 0; i < clCount; i++) {
1132 CellLayout cl = (CellLayout) getChildAt(i);
1133 ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
1134 final int itemCount = swc.getChildCount();
1135 for (int j = 0; j < itemCount; j++) {
1136 View v = swc.getChildAt(j);
1137
1138 if (v != null && v.getTag() instanceof LauncherAppWidgetInfo) {
1139 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
1140 LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
1141 if (lahv != null && lahv.isReinflateRequired()) {
1142 mLauncher.removeAppWidget(info);
1143 // Remove the current widget which is inflated with the wrong orientation
1144 cl.removeView(lahv);
1145 mLauncher.bindAppWidget(info);
1146 }
1147 }
1148 }
1149 }
1150 }
1151
1152 @Override
1153 protected void determineScrollingStart(MotionEvent ev) {
1154 if (!isFinishedSwitchingState()) return;
1155
1156 float deltaX = ev.getX() - mXDown;
1157 float absDeltaX = Math.abs(deltaX);
1158 float absDeltaY = Math.abs(ev.getY() - mYDown);
1159
1160 if (Float.compare(absDeltaX, 0f) == 0) return;
1161
1162 float slope = absDeltaY / absDeltaX;
1163 float theta = (float) Math.atan(slope);
1164
1165 if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
1166 cancelCurrentPageLongPress();
1167 }
1168
1169 boolean passRightSwipesToCustomContent =
1170 (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY;
1171
1172 boolean swipeInIgnoreDirection = isLayoutRtl() ? deltaX < 0 : deltaX > 0;
1173 boolean onCustomContentScreen =
1174 getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID;
1175 if (swipeInIgnoreDirection && onCustomContentScreen && passRightSwipesToCustomContent) {
1176 // Pass swipes to the right to the custom content page.
1177 return;
1178 }
1179
1180 if (onCustomContentScreen && (mCustomContentCallbacks != null)
1181 && !mCustomContentCallbacks.isScrollingAllowed()) {
1182 // Don't allow workspace scrolling if the current custom content screen doesn't allow
1183 // scrolling.
1184 return;
1185 }
1186
1187 if (theta > MAX_SWIPE_ANGLE) {
1188 // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
1189 return;
1190 } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
1191 // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
1192 // increase the touch slop to make it harder to begin scrolling the workspace. This
1193 // results in vertically scrolling widgets to more easily. The higher the angle, the
1194 // more we increase touch slop.
1195 theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
1196 float extraRatio = (float)
1197 Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
1198 super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
1199 } else {
1200 // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
1201 super.determineScrollingStart(ev);
1202 }
1203 }
1204
1205 protected void onPageBeginMoving() {
1206 super.onPageBeginMoving();
1207
1208 if (isHardwareAccelerated()) {
1209 updateChildrenLayersEnabled(false);
1210 } else {
1211 if (mNextPage != INVALID_PAGE) {
1212 // we're snapping to a particular screen
1213 enableChildrenCache(mCurrentPage, mNextPage);
1214 } else {
1215 // this is when user is actively dragging a particular screen, they might
1216 // swipe it either left or right (but we won't advance by more than one screen)
1217 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
1218 }
1219 }
1220 }
1221
1222 protected void onPageEndMoving() {
1223 super.onPageEndMoving();
1224
1225 if (isHardwareAccelerated()) {
1226 updateChildrenLayersEnabled(false);
1227 } else {
1228 clearChildrenCache();
1229 }
1230
1231 if (mDragController.isDragging()) {
1232 if (workspaceInModalState()) {
1233 // If we are in springloaded mode, then force an event to check if the current touch
1234 // is under a new page (to scroll to)
1235 mDragController.forceTouchMove();
1236 }
1237 }
1238
1239 if (mDelayedResizeRunnable != null) {
1240 mDelayedResizeRunnable.run();
1241 mDelayedResizeRunnable = null;
1242 }
1243
1244 if (mDelayedSnapToPageRunnable != null) {
1245 mDelayedSnapToPageRunnable.run();
1246 mDelayedSnapToPageRunnable = null;
1247 }
1248 if (mStripScreensOnPageStopMoving) {
1249 stripEmptyScreens();
1250 mStripScreensOnPageStopMoving = false;
1251 }
1252 }
1253
1254 @Override
1255 protected void notifyPageSwitchListener() {
1256 super.notifyPageSwitchListener();
1257 Launcher.setScreen(getNextPage());
1258
1259 if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
1260 mCustomContentShowing = true;
1261 if (mCustomContentCallbacks != null) {
1262 mCustomContentCallbacks.onShow(false);
1263 mCustomContentShowTime = System.currentTimeMillis();
1264 mLauncher.updateVoiceButtonProxyVisible(false);
1265 }
1266 } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
1267 mCustomContentShowing = false;
1268 if (mCustomContentCallbacks != null) {
1269 mCustomContentCallbacks.onHide();
1270 mLauncher.resetQSBScroll();
1271 mLauncher.updateVoiceButtonProxyVisible(false);
1272 }
1273 }
1274 }
1275
1276 protected CustomContentCallbacks getCustomContentCallbacks() {
1277 return mCustomContentCallbacks;
1278 }
1279
1280 protected void setWallpaperDimension() {
1281 new AsyncTask<Void, Void, Void>() {
1282 public Void doInBackground(Void ... args) {
1283 String spKey = WallpaperCropActivity.getSharedPreferencesKey();
1284 SharedPreferences sp =
1285 mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
1286 LauncherWallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
1287 sp, mLauncher.getWindowManager(), mWallpaperManager,
1288 mLauncher.overrideWallpaperDimensions());
1289 return null;
1290 }
1291 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
1292 }
1293
1294 protected void snapToPage(int whichPage, Runnable r) {
1295 snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r);
1296 }
1297
1298 protected void snapToPage(int whichPage, int duration, Runnable r) {
1299 if (mDelayedSnapToPageRunnable != null) {
1300 mDelayedSnapToPageRunnable.run();
1301 }
1302 mDelayedSnapToPageRunnable = r;
1303 snapToPage(whichPage, duration);
1304 }
1305
1306 public void snapToScreenId(long screenId) {
1307 snapToScreenId(screenId, null);
1308 }
1309
1310 protected void snapToScreenId(long screenId, Runnable r) {
1311 snapToPage(getPageIndexForScreenId(screenId), r);
1312 }
1313
1314 class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
1315 float mFinalOffset = 0.0f;
1316 float mCurrentOffset = 0.5f; // to force an initial update
1317 boolean mWaitingForUpdate;
1318 Choreographer mChoreographer;
1319 Interpolator mInterpolator;
1320 boolean mAnimating;
1321 long mAnimationStartTime;
1322 float mAnimationStartOffset;
1323 private final int ANIMATION_DURATION = 250;
1324 // Don't use all the wallpaper for parallax until you have at least this many pages
1325 private final int MIN_PARALLAX_PAGE_SPAN = 3;
1326 int mNumScreens;
1327
1328 public WallpaperOffsetInterpolator() {
1329 mChoreographer = Choreographer.getInstance();
1330 mInterpolator = new DecelerateInterpolator(1.5f);
1331 }
1332
1333 @Override
1334 public void doFrame(long frameTimeNanos) {
1335 updateOffset(false);
1336 }
1337
1338 private void updateOffset(boolean force) {
1339 if (mWaitingForUpdate || force) {
1340 mWaitingForUpdate = false;
1341 if (computeScrollOffset() && mWindowToken != null) {
1342 try {
1343 mWallpaperManager.setWallpaperOffsets(mWindowToken,
1344 mWallpaperOffset.getCurrX(), 0.5f);
1345 setWallpaperOffsetSteps();
1346 } catch (IllegalArgumentException e) {
1347 Log.e(TAG, "Error updating wallpaper offset: " + e);
1348 }
1349 }
1350 }
1351 }
1352
1353 public boolean computeScrollOffset() {
1354 final float oldOffset = mCurrentOffset;
1355 if (mAnimating) {
1356 long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
1357 float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
1358 float t1 = mInterpolator.getInterpolation(t0);
1359 mCurrentOffset = mAnimationStartOffset +
1360 (mFinalOffset - mAnimationStartOffset) * t1;
1361 mAnimating = durationSinceAnimation < ANIMATION_DURATION;
1362 } else {
1363 mCurrentOffset = mFinalOffset;
1364 }
1365
1366 if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) {
1367 scheduleUpdate();
1368 }
1369 if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) {
1370 return true;
1371 }
1372 return false;
1373 }
1374
1375 private float wallpaperOffsetForCurrentScroll() {
1376 if (getChildCount() <= 1) {
1377 return 0;
1378 }
1379
1380 // Exclude the leftmost page
1381 int emptyExtraPages = numEmptyScreensToIgnore();
1382 int firstIndex = numCustomPages();
1383 // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
1384 int lastIndex = getChildCount() - 1 - emptyExtraPages;
1385 if (isLayoutRtl()) {
1386 int temp = firstIndex;
1387 firstIndex = lastIndex;
1388 lastIndex = temp;
1389 }
1390
1391 int firstPageScrollX = getScrollForPage(firstIndex);
1392 int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX;
1393 if (scrollRange == 0) {
1394 return 0;
1395 } else {
1396 // TODO: do different behavior if it's a live wallpaper?
1397 // Sometimes the left parameter of the pages is animated during a layout transition;
1398 // this parameter offsets it to keep the wallpaper from animating as well
1399 int adjustedScroll =
1400 getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
1401 float offset = Math.min(1, adjustedScroll / (float) scrollRange);
1402 offset = Math.max(0, offset);
1403 // Don't use up all the wallpaper parallax until you have at least
1404 // MIN_PARALLAX_PAGE_SPAN pages
1405 int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
1406 int parallaxPageSpan;
1407 if (mWallpaperIsLiveWallpaper) {
1408 parallaxPageSpan = numScrollingPages - 1;
1409 } else {
1410 parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
1411 }
1412 mNumPagesForWallpaperParallax = parallaxPageSpan;
1413
1414 // On RTL devices, push the wallpaper offset to the right if we don't have enough
1415 // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
1416 int padding = isLayoutRtl() ? parallaxPageSpan - numScrollingPages + 1 : 0;
1417 return offset * (padding + numScrollingPages - 1) / parallaxPageSpan;
1418 }
1419 }
1420
1421 private int numEmptyScreensToIgnore() {
1422 int numScrollingPages = getChildCount() - numCustomPages();
1423 if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
1424 return 1;
1425 } else {
1426 return 0;
1427 }
1428 }
1429
1430 private int getNumScreensExcludingEmptyAndCustom() {
1431 int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages();
1432 return numScrollingPages;
1433 }
1434
1435 public void syncWithScroll() {
1436 float offset = wallpaperOffsetForCurrentScroll();
1437 mWallpaperOffset.setFinalX(offset);
1438 updateOffset(true);
1439 }
1440
1441 public float getCurrX() {
1442 return mCurrentOffset;
1443 }
1444
1445 public float getFinalX() {
1446 return mFinalOffset;
1447 }
1448
1449 private void animateToFinal() {
1450 mAnimating = true;
1451 mAnimationStartOffset = mCurrentOffset;
1452 mAnimationStartTime = System.currentTimeMillis();
1453 }
1454
1455 private void setWallpaperOffsetSteps() {
1456 // Set wallpaper offset steps (1 / (number of screens - 1))
1457 float xOffset = 1.0f / mNumPagesForWallpaperParallax;
1458 if (xOffset != mLastSetWallpaperOffsetSteps) {
1459 mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f);
1460 mLastSetWallpaperOffsetSteps = xOffset;
1461 }
1462 }
1463
1464 public void setFinalX(float x) {
1465 scheduleUpdate();
1466 mFinalOffset = Math.max(0f, Math.min(x, 1.0f));
1467 if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
1468 if (mNumScreens > 0) {
1469 // Don't animate if we're going from 0 screens
1470 animateToFinal();
1471 }
1472 mNumScreens = getNumScreensExcludingEmptyAndCustom();
1473 }
1474 }
1475
1476 private void scheduleUpdate() {
1477 if (!mWaitingForUpdate) {
1478 mChoreographer.postFrameCallback(this);
1479 mWaitingForUpdate = true;
1480 }
1481 }
1482
1483 public void jumpToFinal() {
1484 mCurrentOffset = mFinalOffset;
1485 }
1486 }
1487
1488 @Override
1489 public void computeScroll() {
1490 super.computeScroll();
1491 mWallpaperOffset.syncWithScroll();
1492 }
1493
1494 @Override
1495 public void announceForAccessibility(CharSequence text) {
1496 // Don't announce if apps is on top of us.
1497 if (!mLauncher.isAllAppsVisible()) {
1498 super.announceForAccessibility(text);
1499 }
1500 }
1501
1502 void showOutlines() {
1503 if (!workspaceInModalState() && !mIsSwitchingState) {
1504 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1505 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1506 mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0🔵
1507 mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
1508 mChildrenOutlineFadeInAnimation.start();
1509 }
1510 }
1511
1512 void hideOutlines() {
1513 if (!workspaceInModalState() && !mIsSwitchingState) {
1514 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1515 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1516 mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.🔵
1517 mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
1518 mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
1519 mChildrenOutlineFadeOutAnimation.start();
1520 }
1521 }
1522
1523 public void showOutlinesTemporarily() {
1524 if (!mIsPageMoving && !isTouchActive()) {
1525 snapToPage(mCurrentPage);
1526 }
1527 }
1528
1529 public void setChildrenOutlineAlpha(float alpha) {
1530 mChildrenOutlineAlpha = alpha;
1531 for (int i = 0; i < getChildCount(); i++) {
1532 CellLayout cl = (CellLayout) getChildAt(i);
1533 cl.setBackgroundAlpha(alpha);
1534 }
1535 }
1536
1537 public float getChildrenOutlineAlpha() {
1538 return mChildrenOutlineAlpha;
1539 }
1540
1541 private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1542 final DragLayer dragLayer = mLauncher.getDragLayer();
1543
1544 if (mBackgroundFadeInAnimation != null) {
1545 mBackgroundFadeInAnimation.cancel();
1546 mBackgroundFadeInAnimation = null;
1547 }
1548 if (mBackgroundFadeOutAnimation != null) {
1549 mBackgroundFadeOutAnimation.cancel();
1550 mBackgroundFadeOutAnimation = null;
1551 }
1552 float startAlpha = dragLayer.getBackgroundAlpha();
1553 if (finalAlpha != startAlpha) {
1554 if (animated) {
1555 mBackgroundFadeOutAnimation =
1556 LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
1557 mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1558 public void onAnimationUpdate(ValueAnimator animation) {
1559 dragLayer.setBackgroundAlpha(
1560 ((Float)animation.getAnimatedValue()).floatValue());
1561 }
1562 });
1563 mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
1564 mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1565 mBackgroundFadeOutAnimation.start();
1566 } else {
1567 dragLayer.setBackgroundAlpha(finalAlpha);
1568 }
1569 }
1570 }
1571
1572 float backgroundAlphaInterpolator(float r) {
1573 float pivotA = 0.1f;
1574 float pivotB = 0.4f;
1575 if (r < pivotA) {
1576 return 0;
1577 } else if (r > pivotB) {
1578 return 1.0f;
1579 } else {
1580 return (r - pivotA)/(pivotB - pivotA);
1581 }
1582 }
1583
1584 private void updatePageAlphaValues(int screenCenter) {
1585 boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1586 if (mWorkspaceFadeInAdjacentScreens &&
1587 !workspaceInModalState() &&
1588 !mIsSwitchingState &&
1589 !isInOverscroll) {
1590 for (int i = numCustomPages(); i < getChildCount(); i++) {
1591 CellLayout child = (CellLayout) getChildAt(i);
1592 if (child != null) {
1593 float scrollProgress = getScrollProgress(screenCenter, child, i);
1594 float alpha = 1 - Math.abs(scrollProgress);
1595 child.getShortcutsAndWidgets().setAlpha(alpha);
1596 //child.setBackgroundAlphaMultiplier(1 - alpha);
1597 }
1598 }
1599 }
1600 }
1601
1602 private void setChildrenBackgroundAlphaMultipliers(float a) {
1603 for (int i = 0; i < getChildCount(); i++) {
1604 CellLayout child = (CellLayout) getChildAt(i);
1605 child.setBackgroundAlphaMultiplier(a);
1606 }
1607 }
1608
1609 public boolean hasCustomContent() {
1610 return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
1611 }
1612
1613 public int numCustomPages() {
1614 return hasCustomContent() ? 1 : 0;
1615 }
1616
1617 public boolean isOnOrMovingToCustomContent() {
1618 return hasCustomContent() && getNextPage() == 0;
1619 }
1620
1621 private void updateStateForCustomContent(int screenCenter) {
1622 float translationX = 0;
1623 float progress = 0;
1624 if (hasCustomContent()) {
1625 int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
1626
1627 int scrollDelta = getScrollX() - getScrollForPage(index) -
1628 getLayoutTransitionOffsetForPage(index);
1629 float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
1630 translationX = scrollRange - scrollDelta;
1631 progress = (scrollRange - scrollDelta) / scrollRange;
1632
1633 if (isLayoutRtl()) {
1634 translationX = Math.min(0, translationX);
1635 } else {
1636 translationX = Math.max(0, translationX);
1637 }
1638 progress = Math.max(0, progress);
1639 }
1640
1641 if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return;
1642
1643 CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1644 if (progress > 0 && cc.getVisibility() != VISIBLE && !workspaceInModalState()) {
1645 cc.setVisibility(VISIBLE);
1646 }
1647
1648 mLastCustomContentScrollProgress = progress;
1649
1650 mLauncher.getDragLayer().setBackgroundAlpha(progress * 0.8f);
1651
1652 if (mLauncher.getHotseat() != null) {
1653 mLauncher.getHotseat().setTranslationX(translationX);
1654 }
1655
1656 if (getPageIndicator() != null) {
1657 getPageIndicator().setTranslationX(translationX);
1658 }
1659
1660 if (mCustomContentCallbacks != null) {
1661 mCustomContentCallbacks.onScrollProgressChanged(progress);
1662 }
1663 }
1664
1665 @Override
1666 protected OnClickListener getPageIndicatorClickListener() {
1667 AccessibilityManager am = (AccessibilityManager)
1668 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1669 if (!am.isTouchExplorationEnabled()) {
1670 return null;
1671 }
1672 OnClickListener listener = new OnClickListener() {
1673 @Override
1674 public void onClick(View arg0) {
1675 enterOverviewMode();
1676 }
1677 };
1678 return listener;
1679 }
1680
1681 @Override
1682 protected void screenScrolled(int screenCenter) {
1683 final boolean isRtl = isLayoutRtl();
1684 super.screenScrolled(screenCenter);
1685
1686 updatePageAlphaValues(screenCenter);
1687 updateStateForCustomContent(screenCenter);
1688 enableHwLayersOnVisiblePages();
1689
1690 boolean shouldOverScroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1691
1692 if (shouldOverScroll) {
1693 int index = 0;
1694 final int lowerIndex = 0;
1695 final int upperIndex = getChildCount() - 1;
1696
1697 final boolean isLeftPage = mOverScrollX < 0;
1698 index = (!isRtl && isLeftPage) || (isRtl && !isLeftPage) ? lowerIndex : upperIndex;
1699
1700 CellLayout cl = (CellLayout) getChildAt(index);
1701 float effect = Math.abs(mOverScrollEffect);
1702 cl.setOverScrollAmount(Math.abs(effect), isLeftPage);
1703
1704 mOverscrollEffectSet = true;
1705 } else {
1706 if (mOverscrollEffectSet && getChildCount() > 0) {
1707 mOverscrollEffectSet = false;
1708 ((CellLayout) getChildAt(0)).setOverScrollAmount(0, false);
1709 ((CellLayout) getChildAt(getChildCount() - 1)).setOverScrollAmount(0, false);
1710 }
1711 }
1712 }
1713
1714 @Override
1715 protected void overScroll(float amount) {
1716 boolean shouldOverScroll = (amount < 0 && (!hasCustomContent() || isLayoutRtl())) ||
1717 (amount > 0 && (!hasCustomContent() || !isLayoutRtl()));
1718 if (shouldOverScroll) {
1719 dampedOverScroll(amount);
1720 mOverScrollEffect = acceleratedOverFactor(amount);
1721 } else {
1722 mOverScrollEffect = 0;
1723 }
1724 }
1725
1726 protected void onAttachedToWindow() {
1727 super.onAttachedToWindow();
1728 mWindowToken = getWindowToken();
1729 computeScroll();
1730 mDragController.setWindowToken(mWindowToken);
1731 }
1732
1733 protected void onDetachedFromWindow() {
1734 super.onDetachedFromWindow();
1735 mWindowToken = null;
1736 }
1737
1738 protected void onResume() {
1739 if (getPageIndicator() != null) {
1740 // In case accessibility state has changed, we need to perform this on every
1741 // attach to window
1742 OnClickListener listener = getPageIndicatorClickListener();
1743 if (listener != null) {
1744 getPageIndicator().setOnClickListener(listener);
1745 }
1746 }
1747 AccessibilityManager am = (AccessibilityManager)
1748 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1749 sAccessibilityEnabled = am.isEnabled();
1750
1751 // Update wallpaper dimensions if they were changed since last onResume
1752 // (we also always set the wallpaper dimensions in the constructor)
1753 if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
1754 setWallpaperDimension();
1755 }
1756 mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null;
1757 // Force the wallpaper offset steps to be set again, because another app might have changed
1758 // them
1759 mLastSetWallpaperOffsetSteps = 0f;
1760 }
1761
1762 @Override
1763 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1764 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1765 mWallpaperOffset.syncWithScroll();
1766 mWallpaperOffset.jumpToFinal();
1767 }
1768 super.onLayout(changed, left, top, right, bottom);
1769 }
1770
1771 @Override
1772 protected void onDraw(Canvas canvas) {
1773 super.onDraw(canvas);
1774
1775 // Call back to LauncherModel to finish binding after the first draw
1776 post(mBindPages);
1777 }
1778
1779 @Override
1780 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1781 if (!mLauncher.isAllAppsVisible()) {
1782 final Folder openFolder = getOpenFolder();
1783 if (openFolder != null) {
1784 return openFolder.requestFocus(direction, previouslyFocusedRect);
1785 } else {
1786 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1787 }
1788 }
1789 return false;
1790 }
1791
1792 @Override
1793 public int getDescendantFocusability() {
1794 if (workspaceInModalState()) {
1795 return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1796 }
1797 return super.getDescendantFocusability();
1798 }
1799
1800 @Override
1801 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1802 if (!mLauncher.isAllAppsVisible()) {
1803 final Folder openFolder = getOpenFolder();
1804 if (openFolder != null) {
1805 openFolder.addFocusables(views, direction);
1806 } else {
1807 super.addFocusables(views, direction, focusableMode);
1808 }
1809 }
1810 }
1811
1812 public boolean workspaceInModalState() {
1813 return mState != State.NORMAL;
1814 }
1815
1816 void enableChildrenCache(int fromPage, int toPage) {
1817 if (fromPage > toPage) {
1818 final int temp = fromPage;
1819 fromPage = toPage;
1820 toPage = temp;
1821 }
1822
1823 final int screenCount = getChildCount();
1824
1825 fromPage = Math.max(fromPage, 0);
1826 toPage = Math.min(toPage, screenCount - 1);
1827
1828 for (int i = fromPage; i <= toPage; i++) {
1829 final CellLayout layout = (CellLayout) getChildAt(i);
1830 layout.setChildrenDrawnWithCacheEnabled(true);
1831 layout.setChildrenDrawingCacheEnabled(true);
1832 }
1833 }
1834
1835 void clearChildrenCache() {
1836 final int screenCount = getChildCount();
1837 for (int i = 0; i < screenCount; i++) {
1838 final CellLayout layout = (CellLayout) getChildAt(i);
1839 layout.setChildrenDrawnWithCacheEnabled(false);
1840 // In software mode, we don't want the items to continue to be drawn into bitmaps
1841 if (!isHardwareAccelerated()) {
1842 layout.setChildrenDrawingCacheEnabled(false);
1843 }
1844 }
1845 }
1846
1847 private void updateChildrenLayersEnabled(boolean force) {
1848 boolean small = mState == State.OVERVIEW || mIsSwitchingState;
1849 boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
1850
1851 if (enableChildrenLayers != mChildrenLayersEnabled) {
1852 mChildrenLayersEnabled = enableChildrenLayers;
1853 if (mChildrenLayersEnabled) {
1854 enableHwLayersOnVisiblePages();
1855 } else {
1856 for (int i = 0; i < getPageCount(); i++) {
1857 final CellLayout cl = (CellLayout) getChildAt(i);
1858 cl.enableHardwareLayer(false);
1859 }
1860 }
1861 }
1862 }
1863
1864 private void enableHwLayersOnVisiblePages() {
1865 if (mChildrenLayersEnabled) {
1866 final int screenCount = getChildCount();
1867 getVisiblePages(mTempVisiblePagesRange);
1868 int leftScreen = mTempVisiblePagesRange[0];
1869 int rightScreen = mTempVisiblePagesRange[1];
1870 if (leftScreen == rightScreen) {
1871 // make sure we're caching at least two pages always
1872 if (rightScreen < screenCount - 1) {
1873 rightScreen++;
1874 } else if (leftScreen > 0) {
1875 leftScreen--;
1876 }
1877 }
1878
1879 final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1880 for (int i = 0; i < screenCount; i++) {
1881 final CellLayout layout = (CellLayout) getPageAt(i);
1882
1883 // enable layers between left and right screen inclusive, except for the
1884 // customScreen, which may animate its content during transitions.
1885 boolean enableLayer = layout != customScreen &&
1886 leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
1887 layout.enableHardwareLayer(enableLayer);
1888 }
1889 }
1890 }
1891
1892 public void buildPageHardwareLayers() {
1893 // force layers to be enabled just for the call to buildLayer
1894 updateChildrenLayersEnabled(true);
1895 if (getWindowToken() != null) {
1896 final int childCount = getChildCount();
1897 for (int i = 0; i < childCount; i++) {
1898 CellLayout cl = (CellLayout) getChildAt(i);
1899 cl.buildHardwareLayer();
1900 }
1901 }
1902 updateChildrenLayersEnabled(false);
1903 }
1904
1905 protected void onWallpaperTap(MotionEvent ev) {
1906 final int[] position = mTempCell;
1907 getLocationOnScreen(position);
1908
1909 int pointerIndex = ev.getActionIndex();
1910 position[0] += (int) ev.getX(pointerIndex);
1911 position[1] += (int) ev.getY(pointerIndex);
1912
1913 mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1914 ev.getAction() == MotionEvent.ACTION_UP
1915 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1916 position[0], position[1], 0, null);
1917 }
1918
1919 /*
1920 * This interpolator emulates the rate at which the perceived scale of an object changes
1921 * as its distance from a camera increases. When this interpolator is applied to a scale
1922 * animation on a view, it evokes the sense that the object is shrinking due to moving away
1923 * from the camera.
1924 */
1925 static class ZInterpolator implements TimeInterpolator {
1926 private float focalLength;
1927
1928 public ZInterpolator(float foc) {
1929 focalLength = foc;
1930 }
1931
1932 public float getInterpolation(float input) {
1933 return (1.0f - focalLength / (focalLength + input)) /
1934 (1.0f - focalLength / (focalLength + 1.0f));
1935 }
1936 }
1937
1938 /*
1939 * The exact reverse of ZInterpolator.
1940 */
1941 static class InverseZInterpolator implements TimeInterpolator {
1942 private ZInterpolator zInterpolator;
1943 public InverseZInterpolator(float foc) {
1944 zInterpolator = new ZInterpolator(foc);
1945 }
1946 public float getInterpolation(float input) {
1947 return 1 - zInterpolator.getInterpolation(1 - input);
1948 }
1949 }
1950
1951 /*
1952 * ZInterpolator compounded with an ease-out.
1953 */
1954 static class ZoomOutInterpolator implements TimeInterpolator {
1955 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
1956 private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
1957
1958 public float getInterpolation(float input) {
1959 return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1960 }
1961 }
1962
1963 /*
1964 * InvereZInterpolator compounded with an ease-out.
1965 */
1966 static class ZoomInInterpolator implements TimeInterpolator {
1967 private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
1968 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
1969
1970 public float getInterpolation(float input) {
1971 return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1972 }
1973 }
1974
1975 private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1976
1977 /*
1978 *
1979 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1980 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1981 *
1982 * These methods mark the appropriate pages as accepting drops (which alters their visual
1983 * appearance).
1984 *
1985 */
1986 private static Rect getDrawableBounds(Drawable d) {
1987 Rect bounds = new Rect();
1988 d.copyBounds(bounds);
1989 if (bounds.width() == 0 || bounds.height() == 0) {
1990 bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
1991 } else {
1992 bounds.offsetTo(0, 0);
1993 }
1994 if (d instanceof PreloadIconDrawable) {
1995 int inset = -((PreloadIconDrawable) d).getOutset();
1996 bounds.inset(inset, inset);
1997 }
1998 return bounds;
1999 }
2000
2001 public void onExternalDragStartedWithItem(View v) {
2002 // Compose a drag bitmap with the view scaled to the icon size
2003 LauncherAppState app = LauncherAppState.getInstance();
2004 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2005 int iconSize = grid.iconSizePx;
2006 int bmpWidth = v.getMeasuredWidth();
2007 int bmpHeight = v.getMeasuredHeight();
2008
2009 // If this is a text view, use its drawable instead
2010 if (v instanceof TextView) {
2011 TextView tv = (TextView) v;
2012 Drawable d = tv.getCompoundDrawables()[1];
2013 Rect bounds = getDrawableBounds(d);
2014 bmpWidth = bounds.width();
2015 bmpHeight = bounds.height();
2016 }
2017
2018 // Compose the bitmap to create the icon from
2019 Bitmap b = Bitmap.createBitmap(bmpWidth, bmpHeight,
2020 Bitmap.Config.ARGB_8888);
2021 mCanvas.setBitmap(b);
2022 drawDragView(v, mCanvas, 0);
2023 mCanvas.setBitmap(null);
2024
2025 // The outline is used to visualize where the item will land if dropped
2026 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, iconSize, iconSize, true);
2027 }
2028
2029 public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
2030 int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
2031
2032 // The outline is used to visualize where the item will land if dropped
2033 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
2034 }
2035
2036 public void exitWidgetResizeMode() {
2037 DragLayer dragLayer = mLauncher.getDragLayer();
2038 dragLayer.clearAllResizeFrames();
2039 }
2040
2041 private void initAnimationArrays() {
2042 final int childCount = getChildCount();
2043 if (mLastChildCount == childCount) return;
2044
2045 mOldBackgroundAlphas = new float[childCount];
2046 mOldAlphas = new float[childCount];
2047 mNewBackgroundAlphas = new float[childCount];
2048 mNewAlphas = new float[childCount];
2049 }
2050
2051 Animator getChangeStateAnimation(final State state, boolean animated,
2052 ArrayList<View> layerViews) {
2053 return getChangeStateAnimation(state, animated, 0, -1, layerViews);
2054 }
2055
2056 @Override
2057 protected void getFreeScrollPageRange(int[] range) {
2058 getOverviewModePages(range);
2059 }
2060
2061 private void getOverviewModePages(int[] range) {
2062 int start = numCustomPages();
2063 int end = getChildCount() - 1;
2064
2065 range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
2066 range[1] = Math.max(0, end);
2067 }
2068
2069 protected void onStartReordering() {
2070 super.onStartReordering();
2071 showOutlines();
2072 // Reordering handles its own animations, disable the automatic ones.
2073 disableLayoutTransitions();
2074 }
2075
2076 protected void onEndReordering() {
2077 super.onEndReordering();
2078
2079 if (mLauncher.isWorkspaceLoading()) {
2080 // Invalid and dangerous operation if workspace is loading
2081 return;
2082 }
2083
2084 hideOutlines();
2085 mScreenOrder.clear();
2086 int count = getChildCount();
2087 for (int i = 0; i < count; i++) {
2088 CellLayout cl = ((CellLayout) getChildAt(i));
2089 mScreenOrder.add(getIdForScreen(cl));
2090 }
2091
2092 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
2093
2094 // Re-enable auto layout transitions for page deletion.
2095 enableLayoutTransitions();
2096 }
2097
2098 public boolean isInOverviewMode() {
2099 return mState == State.OVERVIEW;
2100 }
2101
2102 public boolean enterOverviewMode() {
2103 if (mTouchState != TOUCH_STATE_REST) {
2104 return false;
2105 }
2106 enableOverviewMode(true, -1, true);
2107 return true;
2108 }
2109
2110 public void exitOverviewMode(boolean animated) {
2111 exitOverviewMode(-1, animated);
2112 }
2113
2114 public void exitOverviewMode(int snapPage, boolean animated) {
2115 enableOverviewMode(false, snapPage, animated);
2116 }
2117
2118 private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
2119 State finalState = Workspace.State.OVERVIEW;
2120 if (!enable) {
2121 finalState = Workspace.State.NORMAL;
2122 }
2123
2124 Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
2125 if (workspaceAnim != null) {
2126 onTransitionPrepare();
2127 workspaceAnim.addListener(new AnimatorListenerAdapter() {
2128 @Override
2129 public void onAnimationEnd(Animator arg0) {
2130 onTransitionEnd();
2131 }
2132 });
2133 workspaceAnim.start();
2134 }
2135 }
2136
2137 int getOverviewModeTranslationY() {
2138 LauncherAppState app = LauncherAppState.getInstance();
2139 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2140 Rect overviewBar = grid.getOverviewModeButtonBarRect();
2141
2142 int availableHeight = getViewportHeight();
2143 int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
2144 int offsetFromTopEdge = (availableHeight - scaledHeight) / 2;
2145 int offsetToCenterInOverview = (availableHeight - mInsets.top - overviewBar.height()
2146 - scaledHeight) / 2;
2147
2148 return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
2149 }
2150
2151 boolean shouldVoiceButtonProxyBeVisible() {
2152 if (isOnOrMovingToCustomContent()) {
2153 return false;
2154 }
2155 if (mState != State.NORMAL) {
2156 return false;
2157 }
2158 return true;
2159 }
2160
2161 public void updateInteractionForState() {
2162 if (mState != State.NORMAL) {
2163 mLauncher.onInteractionBegin();
2164 } else {
2165 mLauncher.onInteractionEnd();
2166 }
2167 }
2168
2169 private void setState(State state) {
2170 mState = state;
2171 updateInteractionForState();
2172 updateAccessibilityFlags();
2173 }
2174
2175 State getState() {
2176 return mState;
2177 }
2178
2179 private void updateAccessibilityFlags() {
2180 int accessible = mState == State.NORMAL ?
2181 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
2182 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
2183 setImportantForAccessibility(accessible);
2184 }
2185
2186 private static final int HIDE_WORKSPACE_DURATION = 100;
2187
2188 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
2189 return getChangeStateAnimation(state, animated, delay, snapPage, null);
2190 }
2191
2192 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage,
2193 ArrayList<View> layerViews) {
2194 if (mState == state) {
2195 return null;
2196 }
2197
2198 // Initialize animation arrays for the first time if necessary
2199 initAnimationArrays();
2200
2201 AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
2202
2203 final State oldState = mState;
2204 final boolean oldStateIsNormal = (oldState == State.NORMAL);
2205 final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
2206 final boolean oldStateIsNormalHidden = (oldState == State.NORMAL_HIDDEN);
2207 final boolean oldStateIsOverviewHidden = (oldState == State.OVERVIEW_HIDDEN);
2208 final boolean oldStateIsOverview = (oldState == State.OVERVIEW);
2209 setState(state);
2210 final boolean stateIsNormal = (state == State.NORMAL);
2211 final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
2212 final boolean stateIsNormalHidden = (state == State.NORMAL_HIDDEN);
2213 final boolean stateIsOverviewHidden = (state == State.OVERVIEW_HIDDEN);
2214 final boolean stateIsOverview = (state == State.OVERVIEW);
2215 float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
2216 float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1f : 0f;
2217 float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
2218 float finalSearchBarAlpha = !stateIsNormal ? 0f : 1f;
2219 float finalWorkspaceTranslationY = stateIsOverview || stateIsOverviewHidden ?
2220 getOverviewModeTranslationY() : 0;
2221
2222 boolean workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
2223 boolean overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
2224 boolean allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal);
2225 boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
2226 boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
2227
2228 mNewScale = 1.0f;
2229
2230 if (oldStateIsOverview) {
2231 disableFreeScroll();
2232 } else if (stateIsOverview) {
2233 enableFreeScroll();
2234 }
2235
2236 if (state != State.NORMAL) {
2237 if (stateIsSpringLoaded) {
2238 mNewScale = mSpringLoadedShrinkFactor;
2239 } else if (stateIsOverview || stateIsOverviewHidden) {
2240 mNewScale = mOverviewModeShrinkFactor;
2241 }
2242 }
2243
2244 final int duration;
2245 if (workspaceToAllApps || overviewToAllApps) {
2246 duration = HIDE_WORKSPACE_DURATION; //getResources().getInteger(R.integer.config_workspaceUns🔵
2247 } else if (workspaceToOverview || overviewToWorkspace) {
2248 duration = getResources().getInteger(R.integer.config_overviewTransitionTime);
2249 } else {
2250 duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
2251 }
2252
2253 if (snapPage == -1) {
2254 snapPage = getPageNearestToCenterOfScreen();
2255 }
2256 snapToPage(snapPage, duration, mZoomInInterpolator);
2257
2258 for (int i = 0; i < getChildCount(); i++) {
2259 final CellLayout cl = (CellLayout) getChildAt(i);
2260 boolean isCurrentPage = (i == snapPage);
2261 float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
2262 float finalAlpha;
2263 if (stateIsNormalHidden || stateIsOverviewHidden) {
2264 finalAlpha = 0f;
2265 } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
2266 finalAlpha = (i == snapPage || i < numCustomPages()) ? 1f : 0f;
2267 } else {
2268 finalAlpha = 1f;
2269 }
2270
2271 // If we are animating to/from the small state, then hide the side pages and fade the
2272 // current page in
2273 if (!mIsSwitchingState) {
2274 if (workspaceToAllApps || allAppsToWorkspace) {
2275 if (allAppsToWorkspace && isCurrentPage) {
2276 initialAlpha = 0f;
2277 } else if (!isCurrentPage) {
2278 initialAlpha = finalAlpha = 0f;
2279 }
2280 cl.setShortcutAndWidgetAlpha(initialAlpha);
2281 }
2282 }
2283
2284 mOldAlphas[i] = initialAlpha;
2285 mNewAlphas[i] = finalAlpha;
2286 if (animated) {
2287 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
2288 mNewBackgroundAlphas[i] = finalBackgroundAlpha;
2289 } else {
2290 cl.setBackgroundAlpha(finalBackgroundAlpha);
2291 cl.setShortcutAndWidgetAlpha(finalAlpha);
2292 }
2293 }
2294
2295 final View searchBar = mLauncher.getQsbBar();
2296 final View overviewPanel = mLauncher.getOverviewPanel();
2297 final View hotseat = mLauncher.getHotseat();
2298 final View pageIndicator = getPageIndicator();
2299 if (animated) {
2300 LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
2301 scale.scaleX(mNewScale)
2302 .scaleY(mNewScale)
2303 .translationY(finalWorkspaceTranslationY)
2304 .setDuration(duration)
2305 .setInterpolator(mZoomInInterpolator);
2306 anim.play(scale);
2307 for (int index = 0; index < getChildCount(); index++) {
2308 final int i = index;
2309 final CellLayout cl = (CellLayout) getChildAt(i);
2310 float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
2311 if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
2312 cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
2313 cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
2314 } else {
2315 if (layerViews != null) {
2316 layerViews.add(cl);
2317 }
2318 if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
2319 LauncherViewPropertyAnimator alphaAnim =
2320 new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
2321 alphaAnim.alpha(mNewAlphas[i])
2322 .setDuration(duration)
2323 .setInterpolator(mZoomInInterpolator);
2324 anim.play(alphaAnim);
2325 }
2326 if (mOldBackgroundAlphas[i] != 0 ||
2327 mNewBackgroundAlphas[i] != 0) {
2328 ValueAnimator bgAnim =
2329 LauncherAnimUtils.ofFloat(cl, 0f, 1f);
2330 bgAnim.setInterpolator(mZoomInInterpolator);
2331 bgAnim.setDuration(duration);
2332 bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2333 public void onAnimationUpdate(float a, float b) {
2334 cl.setBackgroundAlpha(
2335 a * mOldBackgroundAlphas[i] +
2336 b * mNewBackgroundAlphas[i]);
2337 }
2338 });
2339 anim.play(bgAnim);
2340 }
2341 }
2342 }
2343 Animator pageIndicatorAlpha = null;
2344 if (pageIndicator != null) {
2345 pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
2346 .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
2347 pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator));
2348 } else {
2349 // create a dummy animation so we don't need to do null checks later
2350 pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
2351 }
2352
2353 Animator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat)
2354 .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
2355 hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
2356
2357 Animator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
2358 .alpha(finalSearchBarAlpha).withLayer();
2359 searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
2360
2361 Animator overviewPanelAlpha = new LauncherViewPropertyAnimator(overviewPanel)
2362 .alpha(finalOverviewPanelAlpha).withLayer();
2363 overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
2364
2365 // For animation optimations, we may need to provide the Launcher transition
2366 // with a set of views on which to force build layers in certain scenarios.
2367 hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2368 searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2369 overviewPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2370 if (layerViews != null) {
2371 layerViews.add(hotseat);
2372 layerViews.add(searchBar);
2373 layerViews.add(overviewPanel);
2374 }
2375
2376 if (workspaceToOverview) {
2377 pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
2378 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
2379 overviewPanelAlpha.setInterpolator(null);
2380 } else if (overviewToWorkspace) {
2381 pageIndicatorAlpha.setInterpolator(null);
2382 hotseatAlpha.setInterpolator(null);
2383 overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
2384 }
2385
2386 overviewPanelAlpha.setDuration(duration);
2387 pageIndicatorAlpha.setDuration(duration);
2388 hotseatAlpha.setDuration(duration);
2389 searchBarAlpha.setDuration(duration);
2390
2391 anim.play(overviewPanelAlpha);
2392 anim.play(hotseatAlpha);
2393 anim.play(searchBarAlpha);
2394 anim.play(pageIndicatorAlpha);
2395 anim.setStartDelay(delay);
2396 } else {
2397 overviewPanel.setAlpha(finalOverviewPanelAlpha);
2398 AlphaUpdateListener.updateVisibility(overviewPanel);
2399 hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
2400 AlphaUpdateListener.updateVisibility(hotseat);
2401 if (pageIndicator != null) {
2402 pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
2403 AlphaUpdateListener.updateVisibility(pageIndicator);
2404 }
2405 searchBar.setAlpha(finalSearchBarAlpha);
2406 AlphaUpdateListener.updateVisibility(searchBar);
2407 updateCustomContentVisibility();
2408 setScaleX(mNewScale);
2409 setScaleY(mNewScale);
2410 setTranslationY(finalWorkspaceTranslationY);
2411 }
2412 mLauncher.updateVoiceButtonProxyVisible(false);
2413
2414 if (stateIsNormal) {
2415 animateBackgroundGradient(0f, animated);
2416 } else {
2417 animateBackgroundGradient(getResources().getInteger(
2418 R.integer.config_workspaceScrimAlpha) / 100f, animated);
2419 }
2420 return anim;
2421 }
2422
2423 static class AlphaUpdateListener implements AnimatorUpdateListener, AnimatorListener {
2424 View view;
2425 public AlphaUpdateListener(View v) {
2426 view = v;
2427 }
2428
2429 @Override
2430 public void onAnimationUpdate(ValueAnimator arg0) {
2431 updateVisibility(view);
2432 }
2433
2434 public static void updateVisibility(View view) {
2435 // We want to avoid the extra layout pass by setting the views to GONE unless
2436 // accessibility is on, in which case not setting them to GONE causes a glitch.
2437 int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE;
2438 if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
2439 view.setVisibility(invisibleState);
2440 } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
2441 && view.getVisibility() != VISIBLE) {
2442 view.setVisibility(VISIBLE);
2443 }
2444 }
2445
2446 @Override
2447 public void onAnimationCancel(Animator arg0) {
2448 }
2449
2450 @Override
2451 public void onAnimationEnd(Animator arg0) {
2452 updateVisibility(view);
2453 }
2454
2455 @Override
2456 public void onAnimationRepeat(Animator arg0) {
2457 }
2458
2459 @Override
2460 public void onAnimationStart(Animator arg0) {
2461 // We want the views to be visible for animation, so fade-in/out is visible
2462 view.setVisibility(VISIBLE);
2463 }
2464 }
2465
2466 @Override
2467 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
2468 onTransitionPrepare();
2469 }
2470
2471 @Override
2472 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
2473 }
2474
2475 @Override
2476 public void onLauncherTransitionStep(Launcher l, float t) {
2477 mTransitionProgress = t;
2478 }
2479
2480 @Override
2481 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
2482 onTransitionEnd();
2483 }
2484
2485 private void onTransitionPrepare() {
2486 mIsSwitchingState = true;
2487
2488 // Invalidate here to ensure that the pages are rendered during the state change transition.
2489 invalidate();
2490
2491 updateChildrenLayersEnabled(false);
2492 hideCustomContentIfNecessary();
2493 }
2494
2495 void updateCustomContentVisibility() {
2496 int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
2497 if (hasCustomContent()) {
2498 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
2499 }
2500 }
2501
2502 void showCustomContentIfNecessary() {
2503 boolean show = mState == Workspace.State.NORMAL;
2504 if (show && hasCustomContent()) {
2505 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2506 }
2507 }
2508
2509 void hideCustomContentIfNecessary() {
2510 boolean hide = mState != Workspace.State.NORMAL;
2511 if (hide && hasCustomContent()) {
2512 disableLayoutTransitions();
2513 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
2514 enableLayoutTransitions();
2515 }
2516 }
2517
2518 private void onTransitionEnd() {
2519 mIsSwitchingState = false;
2520 updateChildrenLayersEnabled(false);
2521 showCustomContentIfNecessary();
2522 }
2523
2524 @Override
2525 public View getContent() {
2526 return this;
2527 }
2528
2529 /**
2530 * Draw the View v into the given Canvas.
2531 *
2532 * @param v the view to draw
2533 * @param destCanvas the canvas to draw on
2534 * @param padding the horizontal and vertical padding to use when drawing
2535 */
2536 private static void drawDragView(View v, Canvas destCanvas, int padding) {
2537 final Rect clipRect = sTempRect;
2538 v.getDrawingRect(clipRect);
2539
2540 boolean textVisible = false;
2541
2542 destCanvas.save();
2543 if (v instanceof TextView) {
2544 Drawable d = ((TextView) v).getCompoundDrawables()[1];
2545 Rect bounds = getDrawableBounds(d);
2546 clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding);
2547 destCanvas.translate(padding / 2 - bounds.left, padding / 2 - bounds.top);
2548 d.draw(destCanvas);
2549 } else {
2550 if (v instanceof FolderIcon) {
2551 // For FolderIcons the text can bleed into the icon area, and so we need to
2552 // hide the text completely (which can't be achieved by clipping).
2553 if (((FolderIcon) v).getTextVisible()) {
2554 ((FolderIcon) v).setTextVisible(false);
2555 textVisible = true;
2556 }
2557 }
2558 destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
2559 destCanvas.clipRect(clipRect, Op.REPLACE);
2560 v.draw(destCanvas);
2561
2562 // Restore text visibility of FolderIcon if necessary
2563 if (textVisible) {
2564 ((FolderIcon) v).setTextVisible(true);
2565 }
2566 }
2567 destCanvas.restore();
2568 }
2569
2570 /**
2571 * Returns a new bitmap to show when the given View is being dragged around.
2572 * Responsibility for the bitmap is transferred to the caller.
2573 * @param expectedPadding padding to add to the drag view. If a different padding was used
2574 * its value will be changed
2575 */
2576 public Bitmap createDragBitmap(View v, AtomicInteger expectedPadding) {
2577 Bitmap b;
2578
2579 int padding = expectedPadding.get();
2580 if (v instanceof TextView) {
2581 Drawable d = ((TextView) v).getCompoundDrawables()[1];
2582 Rect bounds = getDrawableBounds(d);
2583 b = Bitmap.createBitmap(bounds.width() + padding,
2584 bounds.height() + padding, Bitmap.Config.ARGB_8888);
2585 expectedPadding.set(padding - bounds.left - bounds.top);
2586 } else {
2587 b = Bitmap.createBitmap(
2588 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2589 }
2590
2591 mCanvas.setBitmap(b);
2592 drawDragView(v, mCanvas, padding);
2593 mCanvas.setBitmap(null);
2594
2595 return b;
2596 }
2597
2598 /**
2599 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2600 * Responsibility for the bitmap is transferred to the caller.
2601 */
2602 private Bitmap createDragOutline(View v, int padding) {
2603 final int outlineColor = getResources().getColor(R.color.outline_color);
2604 final Bitmap b = Bitmap.createBitmap(
2605 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2606
2607 mCanvas.setBitmap(b);
2608 drawDragView(v, mCanvas, padding);
2609 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor);
2610 mCanvas.setBitmap(null);
2611 return b;
2612 }
2613
2614 /**
2615 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2616 * Responsibility for the bitmap is transferred to the caller.
2617 */
2618 private Bitmap createDragOutline(Bitmap orig, int padding, int w, int h,
2619 boolean clipAlpha) {
2620 final int outlineColor = getResources().getColor(R.color.outline_color);
2621 final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
2622 mCanvas.setBitmap(b);
2623
2624 Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
2625 float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
2626 (h - padding) / (float) orig.getHeight());
2627 int scaledWidth = (int) (scaleFactor * orig.getWidth());
2628 int scaledHeight = (int) (scaleFactor * orig.getHeight());
2629 Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2630
2631 // center the image
2632 dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2633
2634 mCanvas.drawBitmap(orig, src, dst, null);
2635 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor,
2636 clipAlpha);
2637 mCanvas.setBitmap(null);
2638
2639 return b;
2640 }
2641
2642 void startDrag(CellLayout.CellInfo cellInfo) {
2643 View child = cellInfo.cell;
2644
2645 // Make sure the drag was started by a long press as opposed to a long click.
2646 if (!child.isInTouchMode()) {
2647 return;
2648 }
2649
2650 mDragInfo = cellInfo;
2651 child.setVisibility(INVISIBLE);
2652 CellLayout layout = (CellLayout) child.getParent().getParent();
2653 layout.prepareChildForDrag(child);
2654
2655 beginDragShared(child, this);
2656 }
2657
2658 public void beginDragShared(View child, DragSource source) {
2659 child.clearFocus();
2660 child.setPressed(false);
2661
2662 // The outline is used to visualize where the item will land if dropped
2663 mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING);
2664
2665 mLauncher.onDragStarted(child);
2666 // The drag bitmap follows the touch point around on the screen
2667 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2668 final Bitmap b = createDragBitmap(child, padding);
2669
2670 final int bmpWidth = b.getWidth();
2671 final int bmpHeight = b.getHeight();
2672
2673 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2674 int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2675 int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
2676 - padding.get() / 2);
2677
2678 LauncherAppState app = LauncherAppState.getInstance();
2679 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2680 Point dragVisualizeOffset = null;
2681 Rect dragRect = null;
2682 if (child instanceof BubbleTextView) {
2683 int iconSize = grid.iconSizePx;
2684 int top = child.getPaddingTop();
2685 int left = (bmpWidth - iconSize) / 2;
2686 int right = left + iconSize;
2687 int bottom = top + iconSize;
2688 dragLayerY += top;
2689 // Note: The drag region is used to calculate drag layer offsets, but the
2690 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2691 dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
2692 dragRect = new Rect(left, top, right, bottom);
2693 } else if (child instanceof FolderIcon) {
2694 int previewSize = grid.folderIconSizePx;
2695 dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2696 }
2697
2698 // Clear the pressed state if necessary
2699 if (child instanceof BubbleTextView) {
2700 BubbleTextView icon = (BubbleTextView) child;
2701 <<<<<<< GitAnalyzerPlus_ours
2702 icon.clearPressedBackground();
2703 ||||||| GitAnalyzerPlus_base
2704 icon.clearPressedOrFocusedBackground();
2705 } else if (child instanceof FolderIcon) {
2706 // Dismiss the folder cling if we haven't already
2707 mLauncher.getLauncherClings().markFolderClingDismissed();
2708 }
2709
2710 if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
2711 String msg = "Drag started with a view that has no tag set. This "
2712 + "will cause a crash (issue 11627249) down the line. "
2713 + "View: " + child + " tag: " + child.getTag();
2714 throw new IllegalStateException(msg);
2715 }
2716
2717 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2718 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2719 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2720
2721 if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2722 mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
2723 }
2724
2725 b.recycle();
2726 }
2727
2728 void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2729 int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2730 View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2731
2732 final int[] cellXY = new int[2];
2733 target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2734 addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2735
2736 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2737 cellXY[1]);
2738 }
2739
2740 public boolean transitionStateShouldAllowDrop() {
2741 return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
2742 }
2743
2744 /**
2745 * {@inheritDoc}
2746 */
2747 public boolean acceptDrop(DragObject d) {
2748 // If it's an external drop (e.g. from All Apps), check if it should be accepted
2749 CellLayout dropTargetLayout = mDropToLayout;
2750 if (d.dragSource != this) {
2751 // Don't accept the drop if we're not over a screen at time of drop
2752 if (dropTargetLayout == null) {
2753 return false;
2754 }
2755 if (!transitionStateShouldAllowDrop()) return false;
2756
2757 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2758 d.dragView, mDragViewVisualCenter);
2759
2760 // We want the point to be mapped to the dragTarget.
2761 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2762 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2763 } else {
2764 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2765 }
2766
2767 int spanX = 1;
2768 int spanY = 1;
2769 if (mDragInfo != null) {
2770 final CellLayout.CellInfo dragCellInfo = mDragInfo;
2771 spanX = dragCellInfo.spanX;
2772 spanY = dragCellInfo.spanY;
2773 } else {
2774 final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
2775 spanX = dragInfo.spanX;
2776 spanY = dragInfo.spanY;
2777 }
2778 =======
2779 icon.clearPressedOrFocusedBackground();
2780 >>>>>>> GitAnalyzerPlus_theirs
2781 }
2782
2783 if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
2784 String msg = "Drag started with a view that has no tag set. This "
2785 + "will cause a crash (issue 11627249) down the line. "
2786 + "View: " + child + " tag: " + child.getTag();
2787 throw new IllegalStateException(msg);
2788 }
2789
2790 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2791 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2792 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2793
2794 if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2795 mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
2796 }
2797
2798 b.recycle();
2799 }
2800
2801 public void beginExternalDragShared(View child, DragSource source) {
2802 LauncherAppState app = LauncherAppState.getInstance();
2803 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2804 int iconSize = grid.iconSizePx;
2805
2806 // Notify launcher of drag start
2807 mLauncher.onDragStarted(child);
2808
2809 // Compose a new drag bitmap that is of the icon size
2810 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2811 final Bitmap tmpB = createDragBitmap(child, padding);
2812 Bitmap b = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
2813 Paint p = new Paint();
2814 p.setFilterBitmap(true);
2815 mCanvas.setBitmap(b);
2816 mCanvas.drawBitmap(tmpB, new Rect(0, 0, tmpB.getWidth(), tmpB.getHeight()),
2817 new Rect(0, 0, iconSize, iconSize), p);
2818 mCanvas.setBitmap(null);
2819
2820 // Find the child's location on the screen
2821 int bmpWidth = tmpB.getWidth();
2822 float iconScale = (float) bmpWidth / iconSize;
2823 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY) * iconScale;
2824 int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2825 int dragLayerY = Math.round(mTempXY[1]);
2826
2827 // Note: The drag region is used to calculate drag layer offsets, but the
2828 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2829 Point dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
2830 Rect dragRect = new Rect(0, 0, iconSize, iconSize);
2831
2832 if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
2833 String msg = "Drag started with a view that has no tag set. This "
2834 + "will cause a crash (issue 11627249) down the line. "
2835 + "View: " + child + " tag: " + child.getTag();
2836 throw new IllegalStateException(msg);
2837 }
2838
2839 // Start the drag
2840 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2841 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2842 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2843
2844 // Recycle temporary bitmaps
2845 tmpB.recycle();
2846 }
2847
2848 void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2849 int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2850 View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2851
2852 final int[] cellXY = new int[2];
2853 target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2854 addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2855
2856 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2857 cellXY[1]);
2858 }
2859
2860 public boolean transitionStateShouldAllowDrop() {
2861 return ((!isSwitchingState() || mTransitionProgress > 0.5f) &&
2862 (mState == State.NORMAL || mState == State.SPRING_LOADED));
2863 }
2864
2865 /**
2866 * {@inheritDoc}
2867 */
2868 public boolean acceptDrop(DragObject d) {
2869 // If it's an external drop (e.g. from All Apps), check if it should be accepted
2870 CellLayout dropTargetLayout = mDropToLayout;
2871 if (d.dragSource != this) {
2872 // Don't accept the drop if we're not over a screen at time of drop
2873 if (dropTargetLayout == null) {
2874 return false;
2875 }
2876 if (!transitionStateShouldAllowDrop()) return false;
2877
2878 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2879 d.dragView, mDragViewVisualCenter);
2880
2881 // We want the point to be mapped to the dragTarget.
2882 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2883 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2884 } else {
2885 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2886 }
2887
2888 int spanX = 1;
2889 int spanY = 1;
2890 if (mDragInfo != null) {
2891 final CellLayout.CellInfo dragCellInfo = mDragInfo;
2892 spanX = dragCellInfo.spanX;
2893 spanY = dragCellInfo.spanY;
2894 } else {
2895 final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
2896 spanX = dragInfo.spanX;
2897 spanY = dragInfo.spanY;
2898 }
2899
2900 int minSpanX = spanX;
2901 int minSpanY = spanY;
2902 if (d.dragInfo instanceof PendingAddWidgetInfo) {
2903 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2904 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2905 }
2906
2907 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2908 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2909 mTargetCell);
2910 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2911 mDragViewVisualCenter[1], mTargetCell);
2912 if (mCreateUserFolderOnDrop && willCreateUserFolder((ItemInfo) d.dragInfo,
2913 dropTargetLayout, mTargetCell, distance, true)) {
2914 return true;
2915 }
2916
2917 if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder((ItemInfo) d.dragInfo,
2918 dropTargetLayout, mTargetCell, distance)) {
2919 return true;
2920 }
2921
2922 int[] resultSpan = new int[2];
2923 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2924 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2925 null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2926 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2927
2928 // Don't accept the drop if there's no room for the item
2929 if (!foundCell) {
2930 // Don't show the message if we are dropping on the AllApps button and the hotseat
2931 // is full
2932 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2933 if (mTargetCell != null && isHotseat) {
2934 Hotseat hotseat = mLauncher.getHotseat();
2935 if (hotseat.isAllAppsButtonRank(
2936 hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2937 return false;
2938 }
2939 }
2940
2941 mLauncher.showOutOfSpaceMessage(isHotseat);
2942 return false;
2943 }
2944 }
2945
2946 long screenId = getIdForScreen(dropTargetLayout);
2947 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2948 commitExtraEmptyScreen();
2949 }
2950
2951 return true;
2952 }
2953
2954 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2955 distance, boolean considerTimeout) {
2956 if (distance > mMaxDistanceForFolderCreation) return false;
2957 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2958
2959 if (dropOverView != null) {
2960 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2961 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2962 return false;
2963 }
2964 }
2965
2966 boolean hasntMoved = false;
2967 if (mDragInfo != null) {
2968 hasntMoved = dropOverView == mDragInfo.cell;
2969 }
2970
2971 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2972 return false;
2973 }
2974
2975 boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2976 boolean willBecomeShortcut =
2977 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2978 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2979
2980 return (aboveShortcut && willBecomeShortcut);
2981 }
2982
2983 boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2984 float distance) {
2985 if (distance > mMaxDistanceForFolderCreation) return false;
2986 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2987
2988 if (dropOverView != null) {
2989 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2990 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2991 return false;
2992 }
2993 }
2994
2995 if (dropOverView instanceof FolderIcon) {
2996 FolderIcon fi = (FolderIcon) dropOverView;
2997 if (fi.acceptDrop(dragInfo)) {
2998 return true;
2999 }
3000 }
3001 return false;
3002 }
3003
3004 boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
3005 int[] targetCell, float distance, boolean external, DragView dragView,
3006 Runnable postAnimationRunnable) {
3007 if (distance > mMaxDistanceForFolderCreation) return false;
3008 View v = target.getChildAt(targetCell[0], targetCell[1]);
3009
3010 boolean hasntMoved = false;
3011 if (mDragInfo != null) {
3012 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
3013 hasntMoved = (mDragInfo.cellX == targetCell[0] &&
3014 mDragInfo.cellY == targetCell[1]) && (cellParent == target);
3015 }
3016
3017 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
3018 mCreateUserFolderOnDrop = false;
3019 final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
3020
3021 boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
3022 boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
3023
3024 if (aboveShortcut && willBecomeShortcut) {
3025 ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
3026 ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
3027 // if the drag started here, we need to remove it from the workspace
3028 if (!external) {
3029 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
3030 }
3031
3032 Rect folderLocation = new Rect();
3033 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
3034 target.removeView(v);
3035
3036 FolderIcon fi =
3037 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
3038 destInfo.cellX = -1;
3039 destInfo.cellY = -1;
3040 sourceInfo.cellX = -1;
3041 sourceInfo.cellY = -1;
3042
3043 // If the dragView is null, we can't animate
3044 boolean animate = dragView != null;
3045 if (animate) {
3046 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
3047 postAnimationRunnable);
3048 } else {
3049 fi.addItem(destInfo);
3050 fi.addItem(sourceInfo);
3051 }
3052 return true;
3053 }
3054 return false;
3055 }
3056
3057 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
3058 float distance, DragObject d, boolean external) {
3059 if (distance > mMaxDistanceForFolderCreation) return false;
3060
3061 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
3062 if (!mAddToExistingFolderOnDrop) return false;
3063 mAddToExistingFolderOnDrop = false;
3064
3065 if (dropOverView instanceof FolderIcon) {
3066 FolderIcon fi = (FolderIcon) dropOverView;
3067 if (fi.acceptDrop(d.dragInfo)) {
3068 fi.onDrop(d);
3069
3070 // if the drag started here, we need to remove it from the workspace
3071 if (!external) {
3072 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
3073 }
3074 return true;
3075 }
3076 }
3077 return false;
3078 }
3079
3080 public void onDrop(final DragObject d) {
3081 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
3082 mDragViewVisualCenter);
3083
3084 CellLayout dropTargetLayout = mDropToLayout;
3085
3086 // We want the point to be mapped to the dragTarget.
3087 if (dropTargetLayout != null) {
3088 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
3089 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3090 } else {
3091 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
3092 }
3093 }
3094
3095 int snapScreen = -1;
3096 boolean resizeOnDrop = false;
3097 if (d.dragSource != this) {
3098 final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
3099 (int) mDragViewVisualCenter[1] };
3100 onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
3101 } else if (mDragInfo != null) {
3102 final View cell = mDragInfo.cell;
3103
3104 Runnable resizeRunnable = null;
3105 if (dropTargetLayout != null && !d.cancelled) {
3106 // Move internally
3107 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
3108 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
3109 long container = hasMovedIntoHotseat ?
3110 LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3111 LauncherSettings.Favorites.CONTAINER_DESKTOP;
3112 long screenId = (mTargetCell[0] < 0) ?
3113 mDragInfo.screenId : getIdForScreen(dropTargetLayout);
3114 int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
3115 int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
3116 // First we find the cell nearest to point at which the item is
3117 // dropped, without any consideration to whether there is an item there.
3118
3119 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
3120 mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
3121 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3122 mDragViewVisualCenter[1], mTargetCell);
3123
3124 // If the item being dropped is a shortcut and the nearest drop
3125 // cell also contains a shortcut, then create a folder with the two shortcuts.
3126 if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
3127 dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
3128 return;
3129 }
3130
3131 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
3132 distance, d, false)) {
3133 return;
3134 }
3135
3136 // Aside from the special case where we're dropping a shortcut onto a shortcut,
3137 // we need to find the nearest cell location that is vacant
3138 ItemInfo item = (ItemInfo) d.dragInfo;
3139 int minSpanX = item.spanX;
3140 int minSpanY = item.spanY;
3141 if (item.minSpanX > 0 && item.minSpanY > 0) {
3142 minSpanX = item.minSpanX;
3143 minSpanY = item.minSpanY;
3144 }
3145
3146 int[] resultSpan = new int[2];
3147 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3148 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
3149 mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
3150
3151 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
3152
3153 // if the widget resizes on drop
3154 if (foundCell && (cell instanceof AppWidgetHostView) &&
3155 (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
3156 resizeOnDrop = true;
3157 item.spanX = resultSpan[0];
3158 item.spanY = resultSpan[1];
3159 AppWidgetHostView awhv = (AppWidgetHostView) cell;
3160 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
3161 resultSpan[1]);
3162 }
3163
3164 if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
3165 snapScreen = getPageIndexForScreenId(screenId);
3166 snapToPage(snapScreen);
3167 }
3168
3169 if (foundCell) {
3170 final ItemInfo info = (ItemInfo) cell.getTag();
3171 if (hasMovedLayouts) {
3172 // Reparent the view
3173 CellLayout parentCell = getParentCellLayoutForView(cell);
3174 if (parentCell != null) {
3175 parentCell.removeView(cell);
3176 } else if (LauncherAppState.isDogfoodBuild()) {
3177 throw new NullPointerException("mDragInfo.cell has null parent");
3178 }
3179 addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
3180 info.spanX, info.spanY);
3181 }
3182
3183 // update the item's position after drop
3184 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
3185 lp.cellX = lp.tmpCellX = mTargetCell[0];
3186 lp.cellY = lp.tmpCellY = mTargetCell[1];
3187 lp.cellHSpan = item.spanX;
3188 lp.cellVSpan = item.spanY;
3189 lp.isLockedToGrid = true;
3190
3191 if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3192 cell instanceof LauncherAppWidgetHostView) {
3193 final CellLayout cellLayout = dropTargetLayout;
3194 // We post this call so that the widget has a chance to be placed
3195 // in its final location
3196
3197 final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
3198 AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
3199 if (pinfo != null &&
3200 pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
3201 final Runnable addResizeFrame = new Runnable() {
3202 public void run() {
3203 DragLayer dragLayer = mLauncher.getDragLayer();
3204 dragLayer.addResizeFrame(info, hostView, cellLayout);
3205 }
3206 };
3207 resizeRunnable = (new Runnable() {
3208 public void run() {
3209 if (!isPageMoving()) {
3210 addResizeFrame.run();
3211 } else {
3212 mDelayedResizeRunnable = addResizeFrame;
3213 }
3214 }
3215 });
3216 }
3217 }
3218
3219 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
3220 lp.cellY, item.spanX, item.spanY);
3221 } else {
3222 // If we can't find a drop location, we return the item to its original position
3223 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
3224 mTargetCell[0] = lp.cellX;
3225 mTargetCell[1] = lp.cellY;
3226 CellLayout layout = (CellLayout) cell.getParent().getParent();
3227 layout.markCellsAsOccupiedForView(cell);
3228 }
3229 }
3230
3231 final CellLayout parent = (CellLayout) cell.getParent().getParent();
3232 final Runnable finalResizeRunnable = resizeRunnable;
3233 // Prepare it to be animated into its new position
3234 // This must be called after the view has been re-parented
3235 final Runnable onCompleteRunnable = new Runnable() {
3236 @Override
3237 public void run() {
3238 mAnimatingViewIntoPlace = false;
3239 updateChildrenLayersEnabled(false);
3240 if (finalResizeRunnable != null) {
3241 finalResizeRunnable.run();
3242 }
3243 }
3244 };
3245 mAnimatingViewIntoPlace = true;
3246 if (d.dragView.hasDrawn()) {
3247 final ItemInfo info = (ItemInfo) cell.getTag();
3248 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
3249 int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
3250 ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3251 animateWidgetDrop(info, parent, d.dragView,
3252 onCompleteRunnable, animationType, cell, false);
3253 } else {
3254 int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
3255 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
3256 onCompleteRunnable, this);
3257 }
3258 } else {
3259 d.deferDragViewCleanupPostAnimation = false;
3260 cell.setVisibility(VISIBLE);
3261 }
3262 parent.onDropChild(cell);
3263 }
3264 }
3265
3266 public void setFinalScrollForPageChange(int pageIndex) {
3267 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3268 if (cl != null) {
3269 mSavedScrollX = getScrollX();
3270 mSavedTranslationX = cl.getTranslationX();
3271 mSavedRotationY = cl.getRotationY();
3272 final int newX = getScrollForPage(pageIndex);
3273 setScrollX(newX);
3274 cl.setTranslationX(0f);
3275 cl.setRotationY(0f);
3276 }
3277 }
3278
3279 public void resetFinalScrollForPageChange(int pageIndex) {
3280 if (pageIndex >= 0) {
3281 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3282 setScrollX(mSavedScrollX);
3283 cl.setTranslationX(mSavedTranslationX);
3284 cl.setRotationY(mSavedRotationY);
3285 }
3286 }
3287
3288 public void getViewLocationRelativeToSelf(View v, int[] location) {
3289 getLocationInWindow(location);
3290 int x = location[0];
3291 int y = location[1];
3292
3293 v.getLocationInWindow(location);
3294 int vX = location[0];
3295 int vY = location[1];
3296
3297 location[0] = vX - x;
3298 location[1] = vY - y;
3299 }
3300
3301 public void onDragEnter(DragObject d) {
3302 mDragEnforcer.onDragEnter();
3303 mCreateUserFolderOnDrop = false;
3304 mAddToExistingFolderOnDrop = false;
3305
3306 mDropToLayout = null;
3307 CellLayout layout = getCurrentDropLayout();
3308 setCurrentDropLayout(layout);
3309 setCurrentDragOverlappingLayout(layout);
3310
3311 if (!workspaceInModalState()) {
3312 mLauncher.getDragLayer().showPageHints();
3313 }
3314 }
3315
3316 /** Return a rect that has the cellWidth/cellHeight (left, top), and
3317 * widthGap/heightGap (right, bottom) */
3318 static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
3319 LauncherAppState app = LauncherAppState.getInstance();
3320 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3321
3322 Display display = launcher.getWindowManager().getDefaultDisplay();
3323 Point smallestSize = new Point();
3324 Point largestSize = new Point();
3325 display.getCurrentSizeRange(smallestSize, largestSize);
3326 int countX = (int) grid.numColumns;
3327 int countY = (int) grid.numRows;
3328 if (orientation == CellLayout.LANDSCAPE) {
3329 if (mLandscapeCellLayoutMetrics == null) {
3330 Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
3331 int width = largestSize.x - padding.left - padding.right;
3332 int height = smallestSize.y - padding.top - padding.bottom;
3333 mLandscapeCellLayoutMetrics = new Rect();
3334 mLandscapeCellLayoutMetrics.set(
3335 grid.calculateCellWidth(width, countX),
3336 grid.calculateCellHeight(height, countY), 0, 0);
3337 }
3338 return mLandscapeCellLayoutMetrics;
3339 } else if (orientation == CellLayout.PORTRAIT) {
3340 if (mPortraitCellLayoutMetrics == null) {
3341 Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
3342 int width = smallestSize.x - padding.left - padding.right;
3343 int height = largestSize.y - padding.top - padding.bottom;
3344 mPortraitCellLayoutMetrics = new Rect();
3345 mPortraitCellLayoutMetrics.set(
3346 grid.calculateCellWidth(width, countX),
3347 grid.calculateCellHeight(height, countY), 0, 0);
3348 }
3349 return mPortraitCellLayoutMetrics;
3350 }
3351 return null;
3352 }
3353
3354 public void onDragExit(DragObject d) {
3355 mDragEnforcer.onDragExit();
3356
3357 // Here we store the final page that will be dropped to, if the workspace in fact
3358 // receives the drop
3359 if (mInScrollArea) {
3360 if (isPageMoving()) {
3361 // If the user drops while the page is scrolling, we should use that page as the
3362 // destination instead of the page that is being hovered over.
3363 mDropToLayout = (CellLayout) getPageAt(getNextPage());
3364 } else {
3365 mDropToLayout = mDragOverlappingLayout;
3366 }
3367 } else {
3368 mDropToLayout = mDragTargetLayout;
3369 }
3370
3371 if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
3372 mCreateUserFolderOnDrop = true;
3373 } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
3374 mAddToExistingFolderOnDrop = true;
3375 }
3376
3377 // Reset the scroll area and previous drag target
3378 onResetScrollArea();
3379 setCurrentDropLayout(null);
3380 setCurrentDragOverlappingLayout(null);
3381
3382 mSpringLoadedDragController.cancel();
3383
3384 if (!mIsPageMoving) {
3385 hideOutlines();
3386 }
3387 mLauncher.getDragLayer().hidePageHints();
3388 }
3389
3390 void setCurrentDropLayout(CellLayout layout) {
3391 if (mDragTargetLayout != null) {
3392 mDragTargetLayout.revertTempState();
3393 mDragTargetLayout.onDragExit();
3394 }
3395 mDragTargetLayout = layout;
3396 if (mDragTargetLayout != null) {
3397 mDragTargetLayout.onDragEnter();
3398 }
3399 cleanupReorder(true);
3400 cleanupFolderCreation();
3401 setCurrentDropOverCell(-1, -1);
3402 }
3403
3404 void setCurrentDragOverlappingLayout(CellLayout layout) {
3405 if (mDragOverlappingLayout != null) {
3406 mDragOverlappingLayout.setIsDragOverlapping(false);
3407 }
3408 mDragOverlappingLayout = layout;
3409 if (mDragOverlappingLayout != null) {
3410 mDragOverlappingLayout.setIsDragOverlapping(true);
3411 }
3412 invalidate();
3413 }
3414
3415 void setCurrentDropOverCell(int x, int y) {
3416 if (x != mDragOverX || y != mDragOverY) {
3417 mDragOverX = x;
3418 mDragOverY = y;
3419 setDragMode(DRAG_MODE_NONE);
3420 }
3421 }
3422
3423 void setDragMode(int dragMode) {
3424 if (dragMode != mDragMode) {
3425 if (dragMode == DRAG_MODE_NONE) {
3426 cleanupAddToFolder();
3427 // We don't want to cancel the re-order alarm every time the target cell changes
3428 // as this feels to slow / unresponsive.
3429 cleanupReorder(false);
3430 cleanupFolderCreation();
3431 } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
3432 cleanupReorder(true);
3433 cleanupFolderCreation();
3434 } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
3435 cleanupAddToFolder();
3436 cleanupReorder(true);
3437 } else if (dragMode == DRAG_MODE_REORDER) {
3438 cleanupAddToFolder();
3439 cleanupFolderCreation();
3440 }
3441 mDragMode = dragMode;
3442 }
3443 }
3444
3445 private void cleanupFolderCreation() {
3446 if (mDragFolderRingAnimator != null) {
3447 mDragFolderRingAnimator.animateToNaturalState();
3448 mDragFolderRingAnimator = null;
3449 }
3450 mFolderCreationAlarm.setOnAlarmListener(null);
3451 mFolderCreationAlarm.cancelAlarm();
3452 }
3453
3454 private void cleanupAddToFolder() {
3455 if (mDragOverFolderIcon != null) {
3456 mDragOverFolderIcon.onDragExit(null);
3457 mDragOverFolderIcon = null;
3458 }
3459 }
3460
3461 private void cleanupReorder(boolean cancelAlarm) {
3462 // Any pending reorders are canceled
3463 if (cancelAlarm) {
3464 mReorderAlarm.cancelAlarm();
3465 }
3466 mLastReorderX = -1;
3467 mLastReorderY = -1;
3468 }
3469
3470 /*
3471 *
3472 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
3473 * coordinate space. The argument xy is modified with the return result.
3474 *
3475 * if cachedInverseMatrix is not null, this method will just use that matrix instead of
3476 * computing it itself; we use this to avoid redundant matrix inversions in
3477 * findMatchingPageForDragOver
3478 *
3479 */
3480 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
3481 xy[0] = xy[0] - v.getLeft();
3482 xy[1] = xy[1] - v.getTop();
3483 }
3484
3485 boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
3486 if (r == null) {
3487 r = new Rect();
3488 }
3489 mTempPt[0] = x;
3490 mTempPt[1] = y;
3491 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3492
3493 LauncherAppState app = LauncherAppState.getInstance();
3494 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3495 r = grid.getHotseatRect();
3496 if (r.contains(mTempPt[0], mTempPt[1])) {
3497 return true;
3498 }
3499 return false;
3500 }
3501
3502 void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
3503 mTempPt[0] = (int) xy[0];
3504 mTempPt[1] = (int) xy[1];
3505 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3506 mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
3507
3508 xy[0] = mTempPt[0];
3509 xy[1] = mTempPt[1];
3510 }
3511
3512 /*
3513 *
3514 * Convert the 2D coordinate xy from this CellLayout's coordinate space to
3515 * the parent View's coordinate space. The argument xy is modified with the return result.
3516 *
3517 */
3518 void mapPointFromChildToSelf(View v, float[] xy) {
3519 xy[0] += v.getLeft();
3520 xy[1] += v.getTop();
3521 }
3522
3523 static private float squaredDistance(float[] point1, float[] point2) {
3524 float distanceX = point1[0] - point2[0];
3525 float distanceY = point2[1] - point2[1];
3526 return distanceX * distanceX + distanceY * distanceY;
3527 }
3528
3529 /*
3530 *
3531 * This method returns the CellLayout that is currently being dragged to. In order to drag
3532 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
3533 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
3534 *
3535 * Return null if no CellLayout is currently being dragged over
3536 *
3537 */
3538 private CellLayout findMatchingPageForDragOver(
3539 DragView dragView, float originX, float originY, boolean exact) {
3540 // We loop through all the screens (ie CellLayouts) and see which ones overlap
3541 // with the item being dragged and then choose the one that's closest to the touch point
3542 final int screenCount = getChildCount();
3543 CellLayout bestMatchingScreen = null;
3544 float smallestDistSoFar = Float.MAX_VALUE;
3545
3546 for (int i = 0; i < screenCount; i++) {
3547 // The custom content screen is not a valid drag over option
3548 if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
3549 continue;
3550 }
3551
3552 CellLayout cl = (CellLayout) getChildAt(i);
3553
3554 final float[] touchXy = {originX, originY};
3555 // Transform the touch coordinates to the CellLayout's local coordinates
3556 // If the touch point is within the bounds of the cell layout, we can return immediately
3557 cl.getMatrix().invert(mTempInverseMatrix);
3558 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
3559
3560 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
3561 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
3562 return cl;
3563 }
3564
3565 if (!exact) {
3566 // Get the center of the cell layout in screen coordinates
3567 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
3568 cellLayoutCenter[0] = cl.getWidth()/2;
3569 cellLayoutCenter[1] = cl.getHeight()/2;
3570 mapPointFromChildToSelf(cl, cellLayoutCenter);
3571
3572 touchXy[0] = originX;
3573 touchXy[1] = originY;
3574
3575 // Calculate the distance between the center of the CellLayout
3576 // and the touch point
3577 float dist = squaredDistance(touchXy, cellLayoutCenter);
3578
3579 if (dist < smallestDistSoFar) {
3580 smallestDistSoFar = dist;
3581 bestMatchingScreen = cl;
3582 }
3583 }
3584 }
3585 return bestMatchingScreen;
3586 }
3587
3588 // This is used to compute the visual center of the dragView. This point is then
3589 // used to visualize drop locations and determine where to drop an item. The idea is that
3590 // the visual center represents the user's interpretation of where the item is, and hence
3591 // is the appropriate point to use when determining drop location.
3592 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
3593 DragView dragView, float[] recycle) {
3594 float res[];
3595 if (recycle == null) {
3596 res = new float[2];
3597 } else {
3598 res = recycle;
3599 }
3600
3601 // First off, the drag view has been shifted in a way that is not represented in the
3602 // x and y values or the x/yOffsets. Here we account for that shift.
3603 x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
3604 y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
3605
3606 // These represent the visual top and left of drag view if a dragRect was provided.
3607 // If a dragRect was not provided, then they correspond to the actual view left and
3608 // top, as the dragRect is in that case taken to be the entire dragView.
3609 // R.dimen.dragViewOffsetY.
3610 int left = x - xOffset;
3611 int top = y - yOffset;
3612
3613 // In order to find the visual center, we shift by half the dragRect
3614 res[0] = left + dragView.getDragRegion().width() / 2;
3615 res[1] = top + dragView.getDragRegion().height() / 2;
3616
3617 return res;
3618 }
3619
3620 private boolean isDragWidget(DragObject d) {
3621 return (d.dragInfo instanceof LauncherAppWidgetInfo ||
3622 d.dragInfo instanceof PendingAddWidgetInfo);
3623 }
3624 private boolean isExternalDragWidget(DragObject d) {
3625 return d.dragSource != this && isDragWidget(d);
3626 }
3627
3628 public void onDragOver(DragObject d) {
3629 // Skip drag over events while we are dragging over side pages
3630 if (mInScrollArea || !transitionStateShouldAllowDrop()) return;
3631
3632 Rect r = new Rect();
3633 CellLayout layout = null;
3634 ItemInfo item = (ItemInfo) d.dragInfo;
3635 if (item == null) {
3636 if (LauncherAppState.isDogfoodBuild()) {
3637 throw new NullPointerException("DragObject has null info");
3638 }
3639 return;
3640 }
3641
3642 // Ensure that we have proper spans for the item that we are dropping
3643 if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
3644 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
3645 d.dragView, mDragViewVisualCenter);
3646
3647 final View child = (mDragInfo == null) ? null : mDragInfo.cell;
3648 // Identify whether we have dragged over a side page
3649 if (workspaceInModalState()) {
3650 if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
3651 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3652 layout = mLauncher.getHotseat().getLayout();
3653 }
3654 }
3655 if (layout == null) {
3656 layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
3657 }
3658 if (layout != mDragTargetLayout) {
3659 setCurrentDropLayout(layout);
3660 setCurrentDragOverlappingLayout(layout);
3661
3662 boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
3663 if (isInSpringLoadedMode) {
3664 if (mLauncher.isHotseatLayout(layout)) {
3665 mSpringLoadedDragController.cancel();
3666 } else {
3667 mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3668 }
3669 }
3670 }
3671 } else {
3672 // Test to see if we are over the hotseat otherwise just use the current page
3673 if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
3674 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3675 layout = mLauncher.getHotseat().getLayout();
3676 }
3677 }
3678 if (layout == null) {
3679 layout = getCurrentDropLayout();
3680 }
3681 if (layout != mDragTargetLayout) {
3682 setCurrentDropLayout(layout);
3683 setCurrentDragOverlappingLayout(layout);
3684 }
3685 }
3686
3687 // Handle the drag over
3688 if (mDragTargetLayout != null) {
3689 // We want the point to be mapped to the dragTarget.
3690 if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3691 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3692 } else {
3693 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
3694 }
3695
3696 ItemInfo info = (ItemInfo) d.dragInfo;
3697
3698 int minSpanX = item.spanX;
3699 int minSpanY = item.spanY;
3700 if (item.minSpanX > 0 && item.minSpanY > 0) {
3701 minSpanX = item.minSpanX;
3702 minSpanY = item.minSpanY;
3703 }
3704
3705 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3706 (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
3707 mDragTargetLayout, mTargetCell);
3708 int reorderX = mTargetCell[0];
3709 int reorderY = mTargetCell[1];
3710
3711 setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3712
3713 float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
3714 mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
3715
3716 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
3717 mTargetCell[1]);
3718
3719 manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
3720 targetCellDistance, dragOverView);
3721
3722 boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
3723 mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
3724 item.spanY, child, mTargetCell);
3725
3726 if (!nearestDropOccupied) {
3727 mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3728 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3729 mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
3730 d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
3731 } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
3732 && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
3733 mLastReorderY != reorderY)) {
3734
3735 int[] resultSpan = new int[2];
3736 mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3737 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
3738 child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
3739
3740 // Otherwise, if we aren't adding to or creating a folder and there's no pending
3741 // reorder, then we schedule a reorder
3742 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
3743 minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
3744 mReorderAlarm.setOnAlarmListener(listener);
3745 mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3746 }
3747
3748 if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
3749 !nearestDropOccupied) {
3750 if (mDragTargetLayout != null) {
3751 mDragTargetLayout.revertTempState();
3752 }
3753 }
3754 }
3755 }
3756
3757 private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
3758 int[] targetCell, float distance, View dragOverView) {
3759 boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
3760 false);
3761
3762 if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3763 !mFolderCreationAlarm.alarmPending()) {
3764 mFolderCreationAlarm.setOnAlarmListener(new
3765 FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
3766 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3767 return;
3768 }
3769
3770 boolean willAddToFolder =
3771 willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
3772
3773 if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3774 mDragOverFolderIcon = ((FolderIcon) dragOverView);
3775 mDragOverFolderIcon.onDragEnter(info);
3776 if (targetLayout != null) {
3777 targetLayout.clearDragOutlines();
3778 }
3779 setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3780 return;
3781 }
3782
3783 if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3784 setDragMode(DRAG_MODE_NONE);
3785 }
3786 if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3787 setDragMode(DRAG_MODE_NONE);
3788 }
3789
3790 return;
3791 }
3792
3793 class FolderCreationAlarmListener implements OnAlarmListener {
3794 CellLayout layout;
3795 int cellX;
3796 int cellY;
3797
3798 public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3799 this.layout = layout;
3800 this.cellX = cellX;
3801 this.cellY = cellY;
3802 }
3803
3804 public void onAlarm(Alarm alarm) {
3805 if (mDragFolderRingAnimator != null) {
3806 // This shouldn't happen ever, but just in case, make sure we clean up the mess.
3807 mDragFolderRingAnimator.animateToNaturalState();
3808 }
3809 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
3810 mDragFolderRingAnimator.setCell(cellX, cellY);
3811 mDragFolderRingAnimator.setCellLayout(layout);
3812 mDragFolderRingAnimator.animateToAcceptState();
3813 layout.showFolderAccept(mDragFolderRingAnimator);
3814 layout.clearDragOutlines();
3815 setDragMode(DRAG_MODE_CREATE_FOLDER);
3816 }
3817 }
3818
3819 class ReorderAlarmListener implements OnAlarmListener {
3820 float[] dragViewCenter;
3821 int minSpanX, minSpanY, spanX, spanY;
3822 DragView dragView;
3823 View child;
3824
3825 public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
3826 int spanY, DragView dragView, View child) {
3827 this.dragViewCenter = dragViewCenter;
3828 this.minSpanX = minSpanX;
3829 this.minSpanY = minSpanY;
3830 this.spanX = spanX;
3831 this.spanY = spanY;
3832 this.child = child;
3833 this.dragView = dragView;
3834 }
3835
3836 public void onAlarm(Alarm alarm) {
3837 int[] resultSpan = new int[2];
3838 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3839 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
3840 mTargetCell);
3841 mLastReorderX = mTargetCell[0];
3842 mLastReorderY = mTargetCell[1];
3843
3844 mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3845 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
3846 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
3847
3848 if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
3849 mDragTargetLayout.revertTempState();
3850 } else {
3851 setDragMode(DRAG_MODE_REORDER);
3852 }
3853
3854 boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
3855 mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3856 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3857 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
3858 dragView.getDragVisualizeOffset(), dragView.getDragRegion());
3859 }
3860 }
3861
3862 @Override
3863 public void getHitRectRelativeToDragLayer(Rect outRect) {
3864 // We want the workspace to have the whole area of the display (it will find the correct
3865 // cell layout to drop to in the existing drag/drop logic.
3866 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3867 }
3868
3869 /**
3870 * Add the item specified by dragInfo to the given layout.
3871 * @return true if successful
3872 */
3873 public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
3874 if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
3875 onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
3876 return true;
3877 }
3878 mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
3879 return false;
3880 }
3881
3882 private void onDropExternal(int[] touchXY, Object dragInfo,
3883 CellLayout cellLayout, boolean insertAtFirst) {
3884 onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
3885 }
3886
3887 /**
3888 * Drop an item that didn't originate on one of the workspace screens.
3889 * It may have come from Launcher (e.g. from all apps or customize), or it may have
3890 * come from another app altogether.
3891 *
3892 * NOTE: This can also be called when we are outside of a drag event, when we want
3893 * to add an item to one of the workspace screens.
3894 */
3895 private void onDropExternal(final int[] touchXY, final Object dragInfo,
3896 final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3897 final Runnable exitSpringLoadedRunnable = new Runnable() {
3898 @Override
3899 public void run() {
3900 mLauncher.exitSpringLoadedDragModeDelayed(true,
3901 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
3902 }
3903 };
3904
3905 ItemInfo info = (ItemInfo) dragInfo;
3906 int spanX = info.spanX;
3907 int spanY = info.spanY;
3908 if (mDragInfo != null) {
3909 spanX = mDragInfo.spanX;
3910 spanY = mDragInfo.spanY;
3911 }
3912
3913 final long container = mLauncher.isHotseatLayout(cellLayout) ?
3914 LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3915 LauncherSettings.Favorites.CONTAINER_DESKTOP;
3916 final long screenId = getIdForScreen(cellLayout);
3917 if (!mLauncher.isHotseatLayout(cellLayout)
3918 && screenId != getScreenIdForPageIndex(mCurrentPage)
3919 && mState != State.SPRING_LOADED) {
3920 snapToScreenId(screenId, null);
3921 }
3922
3923 if (info instanceof PendingAddItemInfo) {
3924 final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3925
3926 boolean findNearestVacantCell = true;
3927 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3928 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3929 cellLayout, mTargetCell);
3930 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3931 mDragViewVisualCenter[1], mTargetCell);
3932 if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
3933 distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
3934 cellLayout, mTargetCell, distance)) {
3935 findNearestVacantCell = false;
3936 }
3937 }
3938
3939 final ItemInfo item = (ItemInfo) d.dragInfo;
3940 boolean updateWidgetSize = false;
3941 if (findNearestVacantCell) {
3942 int minSpanX = item.spanX;
3943 int minSpanY = item.spanY;
3944 if (item.minSpanX > 0 && item.minSpanY > 0) {
3945 minSpanX = item.minSpanX;
3946 minSpanY = item.minSpanY;
3947 }
3948 int[] resultSpan = new int[2];
3949 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
3950 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3951 null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3952
3953 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3954 updateWidgetSize = true;
3955 }
3956 item.spanX = resultSpan[0];
3957 item.spanY = resultSpan[1];
3958 }
3959
3960 Runnable onAnimationCompleteRunnable = new Runnable() {
3961 @Override
3962 public void run() {
3963 // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
3964 // adding an item that may not be dropped right away (due to a config activity)
3965 // we defer the removal until the activity returns.
3966 deferRemoveExtraEmptyScreen();
3967
3968 // When dragging and dropping from customization tray, we deal with creating
3969 // widgets/shortcuts/folders in a slightly different way
3970 switch (pendingInfo.itemType) {
3971 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3972 int span[] = new int[2];
3973 span[0] = item.spanX;
3974 span[1] = item.spanY;
3975 mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3976 container, screenId, mTargetCell, span, null);
3977 break;
3978 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3979 mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3980 container, screenId, mTargetCell, null);
3981 break;
3982 default:
3983 throw new IllegalStateException("Unknown item type: " +
3984 pendingInfo.itemType);
3985 }
3986 }
3987 };
3988 View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3989 ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3990
3991 if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
3992 AppWidgetHostView awhv = (AppWidgetHostView) finalView;
3993 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
3994 item.spanY);
3995 }
3996
3997 int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3998 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3999 ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
4000 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
4001 }
4002 animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
4003 animationStyle, finalView, true);
4004 } else {
4005 // This is for other drag/drop cases, like dragging from All Apps
4006 View view = null;
4007
4008 switch (info.itemType) {
4009 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
4010 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
4011 if (info.container == NO_ID && info instanceof AppInfo) {
4012 // Came from all apps -- make a copy
4013 info = new ShortcutInfo((AppInfo) info);
4014 }
4015 view = mLauncher.createShortcut(R.layout.application, cellLayout,
4016 (ShortcutInfo) info);
4017 break;
4018 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
4019 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
4020 (FolderInfo) info, mIconCache);
4021 break;
4022 default:
4023 throw new IllegalStateException("Unknown item type: " + info.itemType);
4024 }
4025
4026 // First we find the cell nearest to point at which the item is
4027 // dropped, without any consideration to whether there is an item there.
4028 if (touchXY != null) {
4029 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
4030 cellLayout, mTargetCell);
4031 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
4032 mDragViewVisualCenter[1], mTargetCell);
4033 d.postAnimationRunnable = exitSpringLoadedRunnable;
4034 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
4035 true, d.dragView, d.postAnimationRunnable)) {
4036 return;
4037 }
4038 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
4039 true)) {
4040 return;
4041 }
4042 }
4043
4044 if (touchXY != null) {
4045 // when dragging and dropping, just find the closest free spot
4046 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
4047 (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
4048 null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
4049 } else {
4050 cellLayout.findCellForSpan(mTargetCell, 1, 1);
4051 }
4052 // Add the item to DB before adding to screen ensures that the container and other
4053 // values of the info is properly updated.
4054 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
4055 mTargetCell[0], mTargetCell[1]);
4056
4057 addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
4058 info.spanY, insertAtFirst);
4059 cellLayout.onDropChild(view);
4060 cellLayout.getShortcutsAndWidgets().measureChild(view);
4061
4062 if (d.dragView != null) {
4063 // We wrap the animation call in the temporary set and reset of the current
4064 // cellLayout to its final transform -- this means we animate the drag view to
4065 // the correct final location.
4066 setFinalTransitionTransform(cellLayout);
4067 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
4068 exitSpringLoadedRunnable, this);
4069 resetTransitionTransform(cellLayout);
4070 }
4071 }
4072 }
4073
4074 public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
4075 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
4076 widgetInfo.spanY, widgetInfo, false);
4077 int visibility = layout.getVisibility();
4078 layout.setVisibility(VISIBLE);
4079
4080 int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
4081 int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
4082 Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
4083 Bitmap.Config.ARGB_8888);
4084 mCanvas.setBitmap(b);
4085
4086 layout.measure(width, height);
4087 layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
4088 layout.draw(mCanvas);
4089 mCanvas.setBitmap(null);
4090 layout.setVisibility(visibility);
4091 return b;
4092 }
4093
4094 private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
4095 DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
4096 boolean external, boolean scale) {
4097 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
4098 // location and size on the home screen.
4099 int spanX = info.spanX;
4100 int spanY = info.spanY;
4101
4102 Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
4103 loc[0] = r.left;
4104 loc[1] = r.top;
4105
4106 setFinalTransitionTransform(layout);
4107 float cellLayoutScale =
4108 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
4109 resetTransitionTransform(layout);
4110
4111 float dragViewScaleX;
4112 float dragViewScaleY;
4113 if (scale) {
4114 dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
4115 dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
4116 } else {
4117 dragViewScaleX = 1f;
4118 dragViewScaleY = 1f;
4119 }
4120
4121 // The animation will scale the dragView about its center, so we need to center about
4122 // the final location.
4123 loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
4124 loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
4125
4126 scaleXY[0] = dragViewScaleX * cellLayoutScale;
4127 scaleXY[1] = dragViewScaleY * cellLayoutScale;
4128 }
4129
4130 public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
4131 final Runnable onCompleteRunnable, int animationType, final View finalView,
4132 boolean external) {
4133 Rect from = new Rect();
4134 mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
4135
4136 int[] finalPos = new int[2];
4137 float scaleXY[] = new float[2];
4138 boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
4139 getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
4140 external, scalePreview);
4141
4142 Resources res = mLauncher.getResources();
4143 final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
4144
4145 // In the case where we've prebound the widget, we remove it from the DragLayer
4146 if (finalView instanceof AppWidgetHostView && external) {
4147 Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
4148 mLauncher.getDragLayer().removeView(finalView);
4149 }
4150 if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
4151 Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
4152 dragView.setCrossFadeBitmap(crossFadeBitmap);
4153 dragView.crossFade((int) (duration * 0.8f));
4154 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
4155 scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
4156 }
4157
4158 DragLayer dragLayer = mLauncher.getDragLayer();
4159 if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
4160 mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
4161 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
4162 } else {
4163 int endStyle;
4164 if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
4165 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
4166 } else {
4167 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
4168 }
4169
4170 Runnable onComplete = new Runnable() {
4171 @Override
4172 public void run() {
4173 if (finalView != null) {
4174 finalView.setVisibility(VISIBLE);
4175 }
4176 if (onCompleteRunnable != null) {
4177 onCompleteRunnable.run();
4178 }
4179 }
4180 };
4181 dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
4182 finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
4183 duration, this);
4184 }
4185 }
4186
4187 public void setFinalTransitionTransform(CellLayout layout) {
4188 if (isSwitchingState()) {
4189 mCurrentScale = getScaleX();
4190 setScaleX(mNewScale);
4191 setScaleY(mNewScale);
4192 }
4193 }
4194 public void resetTransitionTransform(CellLayout layout) {
4195 if (isSwitchingState()) {
4196 setScaleX(mCurrentScale);
4197 setScaleY(mCurrentScale);
4198 }
4199 }
4200
4201 /**
4202 * Return the current {@link CellLayout}, correctly picking the destination
4203 * screen while a scroll is in progress.
4204 */
4205 public CellLayout getCurrentDropLayout() {
4206 return (CellLayout) getChildAt(getNextPage());
4207 }
4208
4209 /**
4210 * Return the current CellInfo describing our current drag; this method exists
4211 * so that Launcher can sync this object with the correct info when the activity is created/
4212 * destroyed
4213 *
4214 */
4215 public CellLayout.CellInfo getDragInfo() {
4216 return mDragInfo;
4217 }
4218
4219 public int getCurrentPageOffsetFromCustomContent() {
4220 return getNextPage() - numCustomPages();
4221 }
4222
4223 /**
4224 * Calculate the nearest cell where the given object would be dropped.
4225 *
4226 * pixelX and pixelY should be in the coordinate system of layout
4227 */
4228 private int[] findNearestArea(int pixelX, int pixelY,
4229 int spanX, int spanY, CellLayout layout, int[] recycle) {
4230 return layout.findNearestArea(
4231 pixelX, pixelY, spanX, spanY, recycle);
4232 }
4233
4234 void setup(DragController dragController) {
4235 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
4236 mDragController = dragController;
4237
4238 // hardware layers on children are enabled on startup, but should be disabled until
4239 // needed
4240 updateChildrenLayersEnabled(false);
4241 }
4242
4243 /**
4244 * Called at the end of a drag which originated on the workspace.
4245 */
4246 public void onDropCompleted(final View target, final DragObject d,
4247 final boolean isFlingToDelete, final boolean success) {
4248 if (mDeferDropAfterUninstall) {
4249 mDeferredAction = new Runnable() {
4250 public void run() {
4251 onDropCompleted(target, d, isFlingToDelete, success);
4252 mDeferredAction = null;
4253 }
4254 };
4255 return;
4256 }
4257
4258 boolean beingCalledAfterUninstall = mDeferredAction != null;
4259
4260 if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
4261 if (target != this && mDragInfo != null) {
4262 CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
4263 if (parentCell != null) {
4264 parentCell.removeView(mDragInfo.cell);
4265 } else if (LauncherAppState.isDogfoodBuild()) {
4266 throw new NullPointerException("mDragInfo.cell has null parent");
4267 }
4268 if (mDragInfo.cell instanceof DropTarget) {
4269 mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
4270 }
4271 }
4272 } else if (mDragInfo != null) {
4273 CellLayout cellLayout;
4274 if (mLauncher.isHotseatLayout(target)) {
4275 cellLayout = mLauncher.getHotseat().getLayout();
4276 } else {
4277 cellLayout = getScreenWithId(mDragInfo.screenId);
4278 }
4279 if (cellLayout == null && LauncherAppState.isDogfoodBuild()) {
4280 throw new RuntimeException("Invalid state: cellLayout == null in "
4281 + "Workspace#onDropCompleted. Please file a bug. ");
4282 }
4283 if (cellLayout != null) {
4284 cellLayout.onDropChild(mDragInfo.cell);
4285 }
4286 }
4287 if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
4288 && mDragInfo.cell != null) {
4289 mDragInfo.cell.setVisibility(VISIBLE);
4290 }
4291 mDragOutline = null;
4292 mDragInfo = null;
4293 }
4294
4295 public void deferCompleteDropAfterUninstallActivity() {
4296 mDeferDropAfterUninstall = true;
4297 }
4298
4299 /// maybe move this into a smaller part
4300 public void onUninstallActivityReturned(boolean success) {
4301 mDeferDropAfterUninstall = false;
4302 mUninstallSuccessful = success;
4303 if (mDeferredAction != null) {
4304 mDeferredAction.run();
4305 }
4306 }
4307
4308 void updateItemLocationsInDatabase(CellLayout cl) {
4309 int count = cl.getShortcutsAndWidgets().getChildCount();
4310
4311 long screenId = getIdForScreen(cl);
4312 int container = Favorites.CONTAINER_DESKTOP;
4313
4314 if (mLauncher.isHotseatLayout(cl)) {
4315 screenId = -1;
4316 container = Favorites.CONTAINER_HOTSEAT;
4317 }
4318
4319 for (int i = 0; i < count; i++) {
4320 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4321 ItemInfo info = (ItemInfo) v.getTag();
4322 // Null check required as the AllApps button doesn't have an item info
4323 if (info != null && info.requiresDbUpdate) {
4324 info.requiresDbUpdate = false;
4325 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
4326 info.cellY, info.spanX, info.spanY);
4327 }
4328 }
4329 }
4330
4331 ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplic🔵
4332 ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
4333 getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, fals🔵
4334 int count = getChildCount();
4335 for (int i = 0; i < count; i++) {
4336 CellLayout cl = (CellLayout) getChildAt(i);
4337 getUniqueIntents(cl, uniqueIntents, duplicates, false);
4338 }
4339 return uniqueIntents;
4340 }
4341
4342 void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
4343 ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
4344 int count = cl.getShortcutsAndWidgets().getChildCount();
4345
4346 ArrayList<View> children = new ArrayList<View>();
4347 for (int i = 0; i < count; i++) {
4348 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4349 children.add(v);
4350 }
4351
4352 for (int i = 0; i < count; i++) {
4353 View v = children.get(i);
4354 ItemInfo info = (ItemInfo) v.getTag();
4355 // Null check required as the AllApps button doesn't have an item info
4356 if (info instanceof ShortcutInfo) {
4357 ShortcutInfo si = (ShortcutInfo) info;
4358 ComponentName cn = si.intent.getComponent();
4359
4360 Uri dataUri = si.intent.getData();
4361 // If dataUri is not null / empty or if this component isn't one that would
4362 // have previously showed up in the AllApps list, then this is a widget-type
4363 // shortcut, so ignore it.
4364 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4365 continue;
4366 }
4367
4368 if (!uniqueIntents.contains(cn)) {
4369 uniqueIntents.add(cn);
4370 } else {
4371 if (stripDuplicates) {
4372 cl.removeViewInLayout(v);
4373 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4374 }
4375 if (duplicates != null) {
4376 duplicates.add(cn);
4377 }
4378 }
4379 }
4380 if (v instanceof FolderIcon) {
4381 FolderIcon fi = (FolderIcon) v;
4382 ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
4383 for (int j = 0; j < items.size(); j++) {
4384 if (items.get(j).getTag() instanceof ShortcutInfo) {
4385 ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
4386 ComponentName cn = si.intent.getComponent();
4387
4388 Uri dataUri = si.intent.getData();
4389 // If dataUri is not null / empty or if this component isn't one that would
4390 // have previously showed up in the AllApps list, then this is a widget-type
4391 // shortcut, so ignore it.
4392 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4393 continue;
4394 }
4395
4396 if (!uniqueIntents.contains(cn)) {
4397 uniqueIntents.add(cn);
4398 } else {
4399 if (stripDuplicates) {
4400 fi.getFolderInfo().remove(si);
4401 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4402 }
4403 if (duplicates != null) {
4404 duplicates.add(cn);
4405 }
4406 }
4407 }
4408 }
4409 }
4410 }
4411 }
4412
4413 void saveWorkspaceToDb() {
4414 saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
4415 int count = getChildCount();
4416 for (int i = 0; i < count; i++) {
4417 CellLayout cl = (CellLayout) getChildAt(i);
4418 saveWorkspaceScreenToDb(cl);
4419 }
4420 }
4421
4422 void saveWorkspaceScreenToDb(CellLayout cl) {
4423 int count = cl.getShortcutsAndWidgets().getChildCount();
4424
4425 long screenId = getIdForScreen(cl);
4426 int container = Favorites.CONTAINER_DESKTOP;
4427
4428 Hotseat hotseat = mLauncher.getHotseat();
4429 if (mLauncher.isHotseatLayout(cl)) {
4430 screenId = -1;
4431 container = Favorites.CONTAINER_HOTSEAT;
4432 }
4433
4434 for (int i = 0; i < count; i++) {
4435 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4436 ItemInfo info = (ItemInfo) v.getTag();
4437 // Null check required as the AllApps button doesn't have an item info
4438 if (info != null) {
4439 int cellX = info.cellX;
4440 int cellY = info.cellY;
4441 if (container == Favorites.CONTAINER_HOTSEAT) {
4442 cellX = hotseat.getCellXFromOrder((int) info.screenId);
4443 cellY = hotseat.getCellYFromOrder((int) info.screenId);
4444 }
4445 LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
4446 cellY, false);
4447 }
4448 if (v instanceof FolderIcon) {
4449 FolderIcon fi = (FolderIcon) v;
4450 fi.getFolder().addItemLocationsInDatabase();
4451 }
4452 }
4453 }
4454
4455 @Override
4456 public float getIntrinsicIconScaleFactor() {
4457 return 1f;
4458 }
4459
4460 @Override
4461 public boolean supportsFlingToDelete() {
4462 return true;
4463 }
4464
4465 @Override
4466 public boolean supportsAppInfoDropTarget() {
4467 return false;
4468 }
4469
4470 @Override
4471 public boolean supportsDeleteDropTarget() {
4472 return true;
4473 }
4474
4475 @Override
4476 public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
4477 // Do nothing
4478 }
4479
4480 @Override
4481 public void onFlingToDeleteCompleted() {
4482 // Do nothing
4483 }
4484
4485 public boolean isDropEnabled() {
4486 return true;
4487 }
4488
4489 @Override
4490 protected void onRestoreInstanceState(Parcelable state) {
4491 super.onRestoreInstanceState(state);
4492 Launcher.setScreen(mCurrentPage);
4493 }
4494
4495 @Override
4496 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
4497 // We don't dispatch restoreInstanceState to our children using this code path.
4498 // Some pages will be restored immediately as their items are bound immediately, and
4499 // others we will need to wait until after their items are bound.
4500 mSavedStates = container;
4501 }
4502
4503 public void restoreInstanceStateForChild(int child) {
4504 if (mSavedStates != null) {
4505 mRestoredPages.add(child);
4506 CellLayout cl = (CellLayout) getChildAt(child);
4507 if (cl != null) {
4508 cl.restoreInstanceState(mSavedStates);
4509 }
4510 }
4511 }
4512
4513 public void restoreInstanceStateForRemainingPages() {
4514 int count = getChildCount();
4515 for (int i = 0; i < count; i++) {
4516 if (!mRestoredPages.contains(i)) {
4517 restoreInstanceStateForChild(i);
4518 }
4519 }
4520 mRestoredPages.clear();
4521 mSavedStates = null;
4522 }
4523
4524 @Override
4525 public void scrollLeft() {
4526 if (!workspaceInModalState() && !mIsSwitchingState) {
4527 super.scrollLeft();
4528 }
4529 Folder openFolder = getOpenFolder();
4530 if (openFolder != null) {
4531 openFolder.completeDragExit();
4532 }
4533 }
4534
4535 @Override
4536 public void scrollRight() {
4537 if (!workspaceInModalState() && !mIsSwitchingState) {
4538 super.scrollRight();
4539 }
4540 Folder openFolder = getOpenFolder();
4541 if (openFolder != null) {
4542 openFolder.completeDragExit();
4543 }
4544 }
4545
4546 @Override
4547 public boolean onEnterScrollArea(int x, int y, int direction) {
4548 // Ignore the scroll area if we are dragging over the hot seat
4549 boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
4550 if (mLauncher.getHotseat() != null && isPortrait) {
4551 Rect r = new Rect();
4552 mLauncher.getHotseat().getHitRect(r);
4553 if (r.contains(x, y)) {
4554 return false;
4555 }
4556 }
4557
4558 boolean result = false;
4559 if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) {
4560 mInScrollArea = true;
4561
4562 final int page = getNextPage() +
4563 (direction == DragController.SCROLL_LEFT ? -1 : 1);
4564 // We always want to exit the current layout to ensure parity of enter / exit
4565 setCurrentDropLayout(null);
4566
4567 if (0 <= page && page < getChildCount()) {
4568 // Ensure that we are not dragging over to the custom content screen
4569 if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
4570 return false;
4571 }
4572
4573 CellLayout layout = (CellLayout) getChildAt(page);
4574 setCurrentDragOverlappingLayout(layout);
4575
4576 // Workspace is responsible for drawing the edge glow on adjacent pages,
4577 // so we need to redraw the workspace when this may have changed.
4578 invalidate();
4579 result = true;
4580 }
4581 }
4582 return result;
4583 }
4584
4585 @Override
4586 public boolean onExitScrollArea() {
4587 boolean result = false;
4588 if (mInScrollArea) {
4589 invalidate();
4590 CellLayout layout = getCurrentDropLayout();
4591 setCurrentDropLayout(layout);
4592 setCurrentDragOverlappingLayout(layout);
4593
4594 result = true;
4595 mInScrollArea = false;
4596 }
4597 return result;
4598 }
4599
4600 private void onResetScrollArea() {
4601 setCurrentDragOverlappingLayout(null);
4602 mInScrollArea = false;
4603 }
4604
4605 /**
4606 * Returns a specific CellLayout
4607 */
4608 CellLayout getParentCellLayoutForView(View v) {
4609 ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
4610 for (CellLayout layout : layouts) {
4611 if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
4612 return layout;
4613 }
4614 }
4615 return null;
4616 }
4617
4618 /**
4619 * Returns a list of all the CellLayouts in the workspace.
4620 */
4621 ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
4622 ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
4623 int screenCount = getChildCount();
4624 for (int screen = 0; screen < screenCount; screen++) {
4625 layouts.add(((CellLayout) getChildAt(screen)));
4626 }
4627 if (mLauncher.getHotseat() != null) {
4628 layouts.add(mLauncher.getHotseat().getLayout());
4629 }
4630 return layouts;
4631 }
4632
4633 /**
4634 * We should only use this to search for specific children. Do not use this method to modify
4635 * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
4636 * the hotseat and workspace pages
4637 */
4638 ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
4639 ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4640 new ArrayList<ShortcutAndWidgetContainer>();
4641 int screenCount = getChildCount();
4642 for (int screen = 0; screen < screenCount; screen++) {
4643 childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
4644 }
4645 if (mLauncher.getHotseat() != null) {
4646 childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
4647 }
4648 return childrenLayouts;
4649 }
4650
4651 public Folder getFolderForTag(final Object tag) {
4652 return (Folder) getFirstMatch(new ItemOperator() {
4653
4654 @Override
4655 public boolean evaluate(ItemInfo info, View v, View parent) {
4656 return (v instanceof Folder) && (((Folder) v).getInfo() == tag)
4657 && ((Folder) v).getInfo().opened;
4658 }
4659 });
4660 }
4661
4662 public View getViewForTag(final Object tag) {
4663 return getFirstMatch(new ItemOperator() {
4664
4665 @Override
4666 public boolean evaluate(ItemInfo info, View v, View parent) {
4667 return info == tag;
4668 }
4669 });
4670 }
4671
4672 public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
4673 return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
4674
4675 @Override
4676 public boolean evaluate(ItemInfo info, View v, View parent) {
4677 return (info instanceof LauncherAppWidgetInfo) &&
4678 ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
4679 }
4680 });
4681 }
4682
4683 private View getFirstMatch(final ItemOperator operator) {
4684 final View[] value = new View[1];
4685 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4686 @Override
4687 public boolean evaluate(ItemInfo info, View v, View parent) {
4688 if (operator.evaluate(info, v, parent)) {
4689 value[0] = v;
4690 return true;
4691 }
4692 return false;
4693 }
4694 });
4695 return value[0];
4696 }
4697
4698 void clearDropTargets() {
4699 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4700 @Override
4701 public boolean evaluate(ItemInfo info, View v, View parent) {
4702 if (v instanceof DropTarget) {
4703 mDragController.removeDropTarget((DropTarget) v);
4704 }
4705 // not done, process all the shortcuts
4706 return false;
4707 }
4708 });
4709 }
4710
4711 // Removes ALL items that match a given package name, this is usually called when a package
4712 // has been removed and we want to remove all components (widgets, shortcuts, apps) that
4713 // belong to that package.
4714 void removeItemsByPackageName(final ArrayList<String> packages, final UserHandleCompat user) {
4715 final HashSet<String> packageNames = new HashSet<String>();
4716 packageNames.addAll(packages);
4717
4718 // Filter out all the ItemInfos that this is going to affect
4719 final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
4720 final HashSet<ComponentName> cns = new HashSet<ComponentName>();
4721 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4722 for (CellLayout layoutParent : cellLayouts) {
4723 ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4724 int childCount = layout.getChildCount();
4725 for (int i = 0; i < childCount; ++i) {
4726 View view = layout.getChildAt(i);
4727 infos.add((ItemInfo) view.getTag());
4728 }
4729 }
4730 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4731 @Override
4732 public boolean filterItem(ItemInfo parent, ItemInfo info,
4733 ComponentName cn) {
4734 if (packageNames.contains(cn.getPackageName())
4735 && info.user.equals(user)) {
4736 cns.add(cn);
4737 return true;
4738 }
4739 return false;
4740 }
4741 };
4742 LauncherModel.filterItemInfos(infos, filter);
4743
4744 // Remove the affected components
4745 removeItemsByComponentName(cns, user);
4746 }
4747
4748 // Removes items that match the application info specified, when applications are removed
4749 // as a part of an update, this is called to ensure that other widgets and application
4750 // shortcuts are not removed.
4751 void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos, UserHandleCompat user) {
4752 // Just create a hash table of all the specific components that this will affect
4753 HashSet<ComponentName> cns = new HashSet<ComponentName>();
4754 for (AppInfo info : appInfos) {
4755 cns.add(info.componentName);
4756 }
4757
4758 // Remove all the things
4759 removeItemsByComponentName(cns, user);
4760 }
4761
4762 void removeItemsByComponentName(final HashSet<ComponentName> componentNames,
4763 final UserHandleCompat user) {
4764 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4765 for (final CellLayout layoutParent: cellLayouts) {
4766 final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4767
4768 final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4769 for (int j = 0; j < layout.getChildCount(); j++) {
4770 final View view = layout.getChildAt(j);
4771 children.put((ItemInfo) view.getTag(), view);
4772 }
4773
4774 final ArrayList<View> childrenToRemove = new ArrayList<View>();
4775 final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
4776 new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
4777 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4778 @Override
4779 public boolean filterItem(ItemInfo parent, ItemInfo info,
4780 ComponentName cn) {
4781 if (parent instanceof FolderInfo) {
4782 if (componentNames.contains(cn) && info.user.equals(user)) {
4783 FolderInfo folder = (FolderInfo) parent;
4784 ArrayList<ShortcutInfo> appsToRemove;
4785 if (folderAppsToRemove.containsKey(folder)) {
4786 appsToRemove = folderAppsToRemove.get(folder);
4787 } else {
4788 appsToRemove = new ArrayList<ShortcutInfo>();
4789 folderAppsToRemove.put(folder, appsToRemove);
4790 }
4791 appsToRemove.add((ShortcutInfo) info);
4792 return true;
4793 }
4794 } else {
4795 if (componentNames.contains(cn) && info.user.equals(user)) {
4796 childrenToRemove.add(children.get(info));
4797 return true;
4798 }
4799 }
4800 return false;
4801 }
4802 };
4803 LauncherModel.filterItemInfos(children.keySet(), filter);
4804
4805 // Remove all the apps from their folders
4806 for (FolderInfo folder : folderAppsToRemove.keySet()) {
4807 ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4808 for (ShortcutInfo info : appsToRemove) {
4809 folder.remove(info);
4810 }
4811 }
4812
4813 // Remove all the other children
4814 for (View child : childrenToRemove) {
4815 // Note: We can not remove the view directly from CellLayoutChildren as this
4816 // does not re-mark the spaces as unoccupied.
4817 layoutParent.removeViewInLayout(child);
4818 if (child instanceof DropTarget) {
4819 mDragController.removeDropTarget((DropTarget) child);
4820 }
4821 }
4822
4823 if (childrenToRemove.size() > 0) {
4824 layout.requestLayout();
4825 layout.invalidate();
4826 }
4827 }
4828
4829 // Strip all the empty screens
4830 stripEmptyScreens();
4831 }
4832
4833 interface ItemOperator {
4834 /**
4835 * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}.
4836 *
4837 * @param info info for the shortcut
4838 * @param view view for the shortcut
4839 * @param parent containing folder, or null
4840 * @return true if done, false to continue the map
4841 */
4842 public boolean evaluate(ItemInfo info, View view, View parent);
4843 }
4844
4845 /**
4846 * Map the operator over the shortcuts and widgets, return the first-non-null value.
4847 *
4848 * @param recurse true: iterate over folder children. false: op get the folders themselves.
4849 * @param op the operator to map over the shortcuts
4850 */
4851 void mapOverItems(boolean recurse, ItemOperator op) {
4852 ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
4853 final int containerCount = containers.size();
4854 for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
4855 ShortcutAndWidgetContainer container = containers.get(containerIdx);
4856 // map over all the shortcuts on the workspace
4857 final int itemCount = container.getChildCount();
4858 for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
4859 View item = container.getChildAt(itemIdx);
4860 ItemInfo info = (ItemInfo) item.getTag();
4861 if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
4862 FolderIcon folder = (FolderIcon) item;
4863 ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
4864 // map over all the children in the folder
4865 final int childCount = folderChildren.size();
4866 for (int childIdx = 0; childIdx < childCount; childIdx++) {
4867 View child = folderChildren.get(childIdx);
4868 info = (ItemInfo) child.getTag();
4869 if (op.evaluate(info, child, folder)) {
4870 return;
4871 }
4872 }
4873 } else {
4874 if (op.evaluate(info, item, null)) {
4875 return;
4876 }
4877 }
4878 }
4879 }
4880 }
4881
4882 void updateShortcutsAndWidgets(ArrayList<AppInfo> apps) {
4883 // Break the appinfo list per user
4884 final HashMap<UserHandleCompat, ArrayList<AppInfo>> appsPerUser =
4885 new HashMap<UserHandleCompat, ArrayList<AppInfo>>();
4886 for (AppInfo info : apps) {
4887 ArrayList<AppInfo> filtered = appsPerUser.get(info.user);
4888 if (filtered == null) {
4889 filtered = new ArrayList<AppInfo>();
4890 appsPerUser.put(info.user, filtered);
4891 }
4892 filtered.add(info);
4893 }
4894
4895 for (Map.Entry<UserHandleCompat, ArrayList<AppInfo>> entry : appsPerUser.entrySet()) {
4896 updateShortcutsAndWidgetsPerUser(entry.getValue(), entry.getKey());
4897 }
4898 }
4899
4900 private void updateShortcutsAndWidgetsPerUser(ArrayList<AppInfo> apps,
4901 final UserHandleCompat user) {
4902 // Create a map of the apps to test against
4903 final HashMap<ComponentName, AppInfo> appsMap = new HashMap<ComponentName, AppInfo>();
4904 final HashSet<String> pkgNames = new HashSet<String>();
4905 for (AppInfo ai : apps) {
4906 appsMap.put(ai.componentName, ai);
4907 pkgNames.add(ai.componentName.getPackageName());
4908 }
4909 final HashSet<ComponentName> iconsToRemove = new HashSet<ComponentName>();
4910
4911 mapOverItems(MAP_RECURSE, new ItemOperator() {
4912 @Override
4913 public boolean evaluate(ItemInfo info, View v, View parent) {
4914 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
4915 ShortcutInfo shortcutInfo = (ShortcutInfo) info;
4916 ComponentName cn = shortcutInfo.getTargetComponent();
4917 AppInfo appInfo = appsMap.get(cn);
4918 if (user.equals(shortcutInfo.user) && cn != null
4919 && LauncherModel.isShortcutInfoUpdateable(info)
4920 && pkgNames.contains(cn.getPackageName())) {
4921 boolean promiseStateChanged = false;
4922 boolean infoUpdated = false;
4923 if (shortcutInfo.isPromise()) {
4924 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
4925 // Auto install icon
4926 PackageManager pm = getContext().getPackageManager();
4927 ResolveInfo matched = pm.resolveActivity(
4928 new Intent(Intent.ACTION_MAIN)
4929 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
4930 PackageManager.MATCH_DEFAULT_ONLY);
4931 if (matched == null) {
4932 // Try to find the best match activity.
4933 Intent intent = pm.getLaunchIntentForPackage(
4934 cn.getPackageName());
4935 if (intent != null) {
4936 cn = intent.getComponent();
4937 appInfo = appsMap.get(cn);
4938 }
4939
4940 if ((intent == null) || (appsMap == null)) {
4941 // Could not find a default activity. Remove this item.
4942 iconsToRemove.add(shortcutInfo.getTargetComponent());
4943
4944 // process next shortcut.
4945 return false;
4946 }
4947 shortcutInfo.promisedIntent = intent;
4948 }
4949 }
4950
4951 // Restore the shortcut.
4952 shortcutInfo.intent = shortcutInfo.promisedIntent;
4953 shortcutInfo.promisedIntent = null;
4954 shortcutInfo.status &= ~ShortcutInfo.FLAG_RESTORED_ICON
4955 & ~ShortcutInfo.FLAG_AUTOINTALL_ICON
4956 & ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
4957
4958 promiseStateChanged = true;
4959 infoUpdated = true;
4960 shortcutInfo.updateIcon(mIconCache);
4961 LauncherModel.updateItemInDatabase(getContext(), shortcutInfo);
4962 }
4963
4964
4965 if (appInfo != null) {
4966 shortcutInfo.updateIcon(mIconCache);
4967 shortcutInfo.title = appInfo.title.toString();
4968 shortcutInfo.contentDescription = appInfo.contentDescription;
4969 infoUpdated = true;
4970 }
4971
4972 if (infoUpdated) {
4973 BubbleTextView shortcut = (BubbleTextView) v;
4974 shortcut.applyFromShortcutInfo(shortcutInfo,
4975 mIconCache, true, promiseStateChanged);
4976
4977 if (parent != null) {
4978 parent.invalidate();
4979 }
4980 }
4981 }
4982 }
4983 // process all the shortcuts
4984 return false;
4985 }
4986 });
4987
4988 if (!iconsToRemove.isEmpty()) {
4989 removeItemsByComponentName(iconsToRemove, user);
4990 }
4991 if (user.equals(UserHandleCompat.myUserHandle())) {
4992 restorePendingWidgets(pkgNames);
4993 }
4994 }
4995
4996 public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
4997 ArrayList<String> packages = new ArrayList<String>(1);
4998 packages.add(packageName);
4999 LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
5000 removeItemsByPackageName(packages, user);
5001 }
5002
5003 public void updatePackageBadge(final String packageName, final UserHandleCompat user) {
5004 mapOverItems(MAP_RECURSE, new ItemOperator() {
5005 @Override
5006 public boolean evaluate(ItemInfo info, View v, View parent) {
5007 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
5008 ShortcutInfo shortcutInfo = (ShortcutInfo) info;
5009 ComponentName cn = shortcutInfo.getTargetComponent();
5010 if (user.equals(shortcutInfo.user) && cn != null
5011 && shortcutInfo.isPromise()
5012 && packageName.equals(cn.getPackageName())) {
5013 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
5014 // For auto install apps update the icon as well as label.
5015 mIconCache.getTitleAndIcon(shortcutInfo,
5016 shortcutInfo.promisedIntent, user, true);
5017 } else {
5018 // Only update the icon for restored apps.
5019 shortcutInfo.updateIcon(mIconCache);
5020 }
5021 BubbleTextView shortcut = (BubbleTextView) v;
5022 shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, false);
5023
5024 if (parent != null) {
5025 parent.invalidate();
5026 }
5027 }
5028 }
5029 // process all the shortcuts
5030 return false;
5031 }
5032 });
5033 }
5034
5035 public void updatePackageState(ArrayList<PackageInstallInfo> installInfos) {
5036 HashSet<String> completedPackages = new HashSet<String>();
5037
5038 for (final PackageInstallInfo installInfo : installInfos) {
5039 mapOverItems(MAP_RECURSE, new ItemOperator() {
5040 @Override
5041 public boolean evaluate(ItemInfo info, View v, View parent) {
5042 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
5043 ShortcutInfo si = (ShortcutInfo) info;
5044 ComponentName cn = si.getTargetComponent();
5045 if (si.isPromise() && (cn != null)
5046 && installInfo.packageName.equals(cn.getPackageName())) {
5047 si.setInstallProgress(installInfo.progress);
5048 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
5049 // Mark this info as broken.
5050 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
5051 }
5052 ((BubbleTextView)v).applyState(false);
5053 }
5054 } else if (v instanceof PendingAppWidgetHostView
5055 && info instanceof LauncherAppWidgetInfo
5056 && ((LauncherAppWidgetInfo) info).providerName.getPackageName()
5057 .equals(installInfo.packageName)) {
5058 ((LauncherAppWidgetInfo) info).installProgress = installInfo.progress;
5059 ((PendingAppWidgetHostView) v).applyState();
5060 }
5061
5062 // process all the shortcuts
5063 return false;
5064 }
5065 });
5066
5067 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
5068 completedPackages.add(installInfo.packageName);
5069 }
5070 }
5071
5072 // Note that package states are sent only for myUser
5073 if (!completedPackages.isEmpty()) {
5074 restorePendingWidgets(completedPackages);
5075 }
5076 }
5077
5078 private void restorePendingWidgets(final Set<String> installedPackaged) {
5079 final ArrayList<LauncherAppWidgetInfo> changedInfo = new ArrayList<LauncherAppWidgetInfo>();
5080
5081 // Iterate non recursively as widgets can't be inside a folder.
5082 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
5083
5084 @Override
5085 public boolean evaluate(ItemInfo info, View v, View parent) {
5086 if (info instanceof LauncherAppWidgetInfo) {
5087 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
5088 if (widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
5089 && installedPackaged.contains(widgetInfo.providerName.getPackageName())) {
5090
5091 changedInfo.add(widgetInfo);
5092
5093 // Remove the provider not ready flag
5094 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
5095 LauncherModel.updateItemInDatabase(getContext(), widgetInfo);
5096 }
5097 }
5098 // process all the widget
5099 return false;
5100 }
5101 });
5102 if (!changedInfo.isEmpty()) {
5103 DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
5104 mLauncher.getAppWidgetHost());
5105 if (LauncherModel.findAppWidgetProviderInfoWithComponent(getContext(),
5106 changedInfo.get(0).providerName) != null) {
5107 // Re-inflate the widgets which have changed status
5108 widgetRefresh.run();
5109 } else {
5110 // widgetRefresh will automatically run when the packages are updated.
5111 }
5112 }
5113 }
5114
5115 private void moveToScreen(int page, boolean animate) {
5116 if (!workspaceInModalState()) {
5117 if (animate) {
5118 snapToPage(page);
5119 } else {
5120 setCurrentPage(page);
5121 }
5122 }
5123 View child = getChildAt(page);
5124 if (child != null) {
5125 child.requestFocus();
5126 }
5127 }
5128
5129 void moveToDefaultScreen(boolean animate) {
5130 moveToScreen(mDefaultPage, animate);
5131 }
5132
5133 void moveToCustomContentScreen(boolean animate) {
5134 if (hasCustomContent()) {
5135 int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
5136 if (animate) {
5137 snapToPage(ccIndex);
5138 } else {
5139 setCurrentPage(ccIndex);
5140 }
5141 View child = getChildAt(ccIndex);
5142 if (child != null) {
5143 child.requestFocus();
5144 }
5145 }
5146 exitWidgetResizeMode();
5147 }
5148
5149 @Override
5150 protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
5151 long screenId = getScreenIdForPageIndex(pageIndex);
5152 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
5153 int count = mScreenOrder.size() - numCustomPages();
5154 if (count > 1) {
5155 return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
5156 R.drawable.ic_pageindicator_add);
5157 }
5158 }
5159
5160 return super.getPageIndicatorMarker(pageIndex);
5161 }
5162
5163 @Override
5164 public void syncPages() {
5165 }
5166
5167 @Override
5168 public void syncPageItems(int page, boolean immediate) {
5169 }
5170
5171 protected String getPageIndicatorDescription() {
5172 String settings = getResources().getString(R.string.settings_button_text);
5173 return getCurrentPageDescription() + ", " + settings;
5174 }
5175
5176 protected String getCurrentPageDescription() {
5177 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
5178 int delta = numCustomPages();
5179 if (hasCustomContent() && getNextPage() == 0) {
5180 return mCustomContentDescription;
5181 }
5182 return String.format(getContext().getString(R.string.workspace_scroll_format),
5183 page + 1 - delta, getChildCount() - delta);
5184 }
5185
5186 public void getLocationInDragLayer(int[] loc) {
5187 mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
5188 }
5189
5190 /**
5191 * Used as a workaround to ensure that the AppWidgetService receives the
5192 * PACKAGE_ADDED broadcast before updating widgets.
5193 */
5194 private class DeferredWidgetRefresh implements Runnable {
5195 private final ArrayList<LauncherAppWidgetInfo> mInfos;
5196 private final LauncherAppWidgetHost mHost;
5197 private final Handler mHandler;
5198
5199 private boolean mRefreshPending;
5200
5201 public DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
5202 LauncherAppWidgetHost host) {
5203 mInfos = infos;
5204 mHost = host;
5205 mHandler = new Handler();
5206 mRefreshPending = true;
5207
5208 mHost.addProviderChangeListener(this);
5209 // Force refresh after 10 seconds, if we don't get the provider changed event.
5210 // This could happen when the provider is no longer available in the app.
5211 mHandler.postDelayed(this, 10000);
5212 }
5213
5214 @Override
5215 public void run() {
5216 mHost.removeProviderChangeListener(this);
5217 mHandler.removeCallbacks(this);
5218
5219 if (!mRefreshPending) {
5220 return;
5221 }
5222
5223 mRefreshPending = false;
5224
5225 for (LauncherAppWidgetInfo info : mInfos) {
5226 if (info.hostView instanceof PendingAppWidgetHostView) {
5227 PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView;
5228 mLauncher.removeAppWidget(info);
5229
5230 CellLayout cl = (CellLayout) view.getParent().getParent();
5231 // Remove the current widget
5232 cl.removeView(view);
5233 mLauncher.bindAppWidget(info);
5234 }
5235 }
5236 }
5237 }
5238 } |
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.animation.Animator;
20 import android.animation.Animator.AnimatorListener;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.animation.LayoutTransition;
24 import android.animation.ObjectAnimator;
25 import android.animation.PropertyValuesHolder;
26 import android.animation.TimeInterpolator;
27 import android.animation.ValueAnimator;
28 import android.animation.ValueAnimator.AnimatorUpdateListener;
29 import android.app.WallpaperManager;
30 import android.appwidget.AppWidgetHostView;
31 import android.appwidget.AppWidgetProviderInfo;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.SharedPreferences;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.res.Resources;
39 import android.content.res.TypedArray;
40 import android.graphics.Bitmap;
41 import android.graphics.Canvas;
42 import android.graphics.Matrix;
43 import android.graphics.Paint;
44 import android.graphics.Point;
45 import android.graphics.PointF;
46 import android.graphics.Rect;
47 import android.graphics.Region.Op;
48 import android.graphics.drawable.Drawable;
49 import android.net.Uri;
50 import android.os.AsyncTask;
51 import android.os.Handler;
52 import android.os.IBinder;
53 import android.os.Parcelable;
54 import android.support.v4.view.ViewCompat;
55 import android.util.AttributeSet;
56 import android.util.Log;
57 import android.util.SparseArray;
58 import android.view.Choreographer;
59 import android.view.Display;
60 import android.view.MotionEvent;
61 import android.view.View;
62 import android.view.ViewGroup;
63 import android.view.accessibility.AccessibilityManager;
64 import android.view.animation.DecelerateInterpolator;
65 import android.view.animation.Interpolator;
66 import android.widget.TextView;
67
68 import com.android.launcher3.FolderIcon.FolderRingAnimator;
69 import com.android.launcher3.Launcher.CustomContentCallbacks;
70 import com.android.launcher3.LauncherSettings.Favorites;
71 import com.android.launcher3.compat.PackageInstallerCompat;
72 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
73 import com.android.launcher3.compat.UserHandleCompat;
74
75 import java.util.ArrayList;
76 import java.util.HashMap;
77 import java.util.HashSet;
78 import java.util.Iterator;
79 import java.util.Map;
80 import java.util.Set;
81 import java.util.concurrent.atomic.AtomicInteger;
82
83 /**
84 * The workspace is a wide area with a wallpaper and a finite number of pages.
85 * Each page contains a number of icons, folders or widgets the user can
86 * interact with. A workspace is meant to be used with a fixed width only.
87 */
88 public class Workspace extends SmoothPagedView
89 implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
90 DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
91 Insettable {
92 private static final String TAG = "Launcher.Workspace";
93
94 // Y rotation to apply to the workspace screens
95 private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
96
97 private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
98 private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
99 private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
100
101 protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
102 protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
103
104 private static final int BACKGROUND_FADE_OUT_DURATION = 350;
105 private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
106 private static final int FLING_THRESHOLD_VELOCITY = 500;
107
108 private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
109
110 static final boolean MAP_NO_RECURSE = false;
111 static final boolean MAP_RECURSE = true;
112
113 // These animators are used to fade the children's outlines
114 private ObjectAnimator mChildrenOutlineFadeInAnimation;
115 private ObjectAnimator mChildrenOutlineFadeOutAnimation;
116 private float mChildrenOutlineAlpha = 0;
117
118 // These properties refer to the background protection gradient used for AllApps and Customize
119 private ValueAnimator mBackgroundFadeInAnimation;
120 private ValueAnimator mBackgroundFadeOutAnimation;
121
122 private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
123 private long mTouchDownTime = -1;
124 private long mCustomContentShowTime = -1;
125
126 private LayoutTransition mLayoutTransition;
127 private final WallpaperManager mWallpaperManager;
128 private IBinder mWindowToken;
129
130 private int mOriginalDefaultPage;
131 private int mDefaultPage;
132
133 private ShortcutAndWidgetContainer mDragSourceInternal;
134 private static boolean sAccessibilityEnabled;
135
136 // The screen id used for the empty screen always present to the right.
137 final static long EXTRA_EMPTY_SCREEN_ID = -201;
138 private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
139
140 private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
141 private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
142
143 private Runnable mRemoveEmptyScreenRunnable;
144 private boolean mDeferRemoveExtraEmptyScreen = false;
145
146 /**
147 * CellInfo for the cell that is currently being dragged
148 */
149 private CellLayout.CellInfo mDragInfo;
150
151 /**
152 * Target drop area calculated during last acceptDrop call.
153 */
154 private int[] mTargetCell = new int[2];
155 private int mDragOverX = -1;
156 private int mDragOverY = -1;
157
158 static Rect mLandscapeCellLayoutMetrics = null;
159 static Rect mPortraitCellLayoutMetrics = null;
160
161 CustomContentCallbacks mCustomContentCallbacks;
162 boolean mCustomContentShowing;
163 private float mLastCustomContentScrollProgress = -1f;
164 private String mCustomContentDescription = "";
165
166 /**
167 * The CellLayout that is currently being dragged over
168 */
169 private CellLayout mDragTargetLayout = null;
170 /**
171 * The CellLayout that we will show as glowing
172 */
173 private CellLayout mDragOverlappingLayout = null;
174
175 /**
176 * The CellLayout which will be dropped to
177 */
178 private CellLayout mDropToLayout = null;
179
180 private Launcher mLauncher;
181 private IconCache mIconCache;
182 private DragController mDragController;
183
184 // These are temporary variables to prevent having to allocate a new object just to
185 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
186 private int[] mTempCell = new int[2];
187 private int[] mTempPt = new int[2];
188 private int[] mTempEstimate = new int[2];
189 private float[] mDragViewVisualCenter = new float[2];
190 private float[] mTempCellLayoutCenterCoordinates = new float[2];
191 private Matrix mTempInverseMatrix = new Matrix();
192
193 private SpringLoadedDragController mSpringLoadedDragController;
194 private float mSpringLoadedShrinkFactor;
195 private float mOverviewModeShrinkFactor;
196
197 // State variable that indicates whether the pages are small (ie when you're
198 // in all apps or customize mode)
199
200 enum State { NORMAL, NORMAL_HIDDEN, SPRING_LOADED, OVERVIEW, OVERVIEW_HIDDEN};
201 private State mState = State.NORMAL;
202 private boolean mIsSwitchingState = false;
203
204 boolean mAnimatingViewIntoPlace = false;
205 boolean mIsDragOccuring = false;
206 boolean mChildrenLayersEnabled = true;
207
208 private boolean mStripScreensOnPageStopMoving = false;
209
210 /** Is the user is dragging an item near the edge of a page? */
211 private boolean mInScrollArea = false;
212
213 private HolographicOutlineHelper mOutlineHelper;
214 private Bitmap mDragOutline = null;
215 private static final Rect sTempRect = new Rect();
216 private final int[] mTempXY = new int[2];
217 private int[] mTempVisiblePagesRange = new int[2];
218 private boolean mOverscrollEffectSet;
219 public static final int DRAG_BITMAP_PADDING = 2;
220 private boolean mWorkspaceFadeInAdjacentScreens;
221
222 WallpaperOffsetInterpolator mWallpaperOffset;
223 private boolean mWallpaperIsLiveWallpaper;
224 private int mNumPagesForWallpaperParallax;
225 private float mLastSetWallpaperOffsetSteps = 0;
226
227 private Runnable mDelayedResizeRunnable;
228 private Runnable mDelayedSnapToPageRunnable;
229 private Point mDisplaySize = new Point();
230 private int mCameraDistance;
231
232 // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
233 private static final int FOLDER_CREATION_TIMEOUT = 0;
234 public static final int REORDER_TIMEOUT = 350;
235 private final Alarm mFolderCreationAlarm = new Alarm();
236 private final Alarm mReorderAlarm = new Alarm();
237 private FolderRingAnimator mDragFolderRingAnimator = null;
238 private FolderIcon mDragOverFolderIcon = null;
239 private boolean mCreateUserFolderOnDrop = false;
240 private boolean mAddToExistingFolderOnDrop = false;
241 private DropTarget.DragEnforcer mDragEnforcer;
242 private float mMaxDistanceForFolderCreation;
243
244 private final Canvas mCanvas = new Canvas();
245
246 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
247 private float mXDown;
248 private float mYDown;
249 final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
250 final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
251 final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
252
253 // Relating to the animation of items being dropped externally
254 public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
255 public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
256 public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
257 public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
258 public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
259
260 // Related to dragging, folder creation and reordering
261 private static final int DRAG_MODE_NONE = 0;
262 private static final int DRAG_MODE_CREATE_FOLDER = 1;
263 private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
264 private static final int DRAG_MODE_REORDER = 3;
265 private int mDragMode = DRAG_MODE_NONE;
266 private int mLastReorderX = -1;
267 private int mLastReorderY = -1;
268
269 private SparseArray<Parcelable> mSavedStates;
270 private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
271
272 // These variables are used for storing the initial and final values during workspace animations
273 private int mSavedScrollX;
274 private float mSavedRotationY;
275 private float mSavedTranslationX;
276
277 private float mCurrentScale;
278 private float mNewScale;
279 private float[] mOldBackgroundAlphas;
280 private float[] mOldAlphas;
281 private float[] mNewBackgroundAlphas;
282 private float[] mNewAlphas;
283 private int mLastChildCount = -1;
284 private float mTransitionProgress;
285
286 float mOverScrollEffect = 0f;
287
288 private Runnable mDeferredAction;
289 private boolean mDeferDropAfterUninstall;
290 private boolean mUninstallSuccessful;
291
292 private final Runnable mBindPages = new Runnable() {
293 @Override
294 public void run() {
295 mLauncher.getModel().bindRemainingSynchronousPages();
296 }
297 };
298
299 /**
300 * Used to inflate the Workspace from XML.
301 *
302 * @param context The application's context.
303 * @param attrs The attributes set containing the Workspace's customization values.
304 */
305 public Workspace(Context context, AttributeSet attrs) {
306 this(context, attrs, 0);
307 }
308
309 /**
310 * Used to inflate the Workspace from XML.
311 *
312 * @param context The application's context.
313 * @param attrs The attributes set containing the Workspace's customization values.
314 * @param defStyle Unused.
315 */
316 public Workspace(Context context, AttributeSet attrs, int defStyle) {
317 super(context, attrs, defStyle);
318 mContentIsRefreshable = false;
319
320 mOutlineHelper = HolographicOutlineHelper.obtain(context);
321
322 mDragEnforcer = new DropTarget.DragEnforcer(context);
323 // With workspace, data is available straight from the get-go
324 setDataIsReady();
325
326 mLauncher = (Launcher) context;
327 final Resources res = getResources();
328 mWorkspaceFadeInAdjacentScreens = LauncherAppState.getInstance().getDynamicGrid().
329 getDeviceProfile().shouldFadeAdjacentWorkspaceScreens();
330 mFadeInAdjacentScreens = false;
331 mWallpaperManager = WallpaperManager.getInstance(context);
332
333 LauncherAppState app = LauncherAppState.getInstance();
334 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
335 TypedArray a = context.obtainStyledAttributes(attrs,
336 R.styleable.Workspace, defStyle, 0);
337 mSpringLoadedShrinkFactor =
338 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
339 mOverviewModeShrinkFactor = grid.getOverviewModeScale();
340 mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
341 mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
342 a.recycle();
343
344 setOnHierarchyChangeListener(this);
345 setHapticFeedbackEnabled(false);
346
347 initWorkspace();
348
349 // Disable multitouch across the workspace/all apps/customize tray
350 setMotionEventSplittingEnabled(true);
351 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
352 }
353
354 @Override
355 public void setInsets(Rect insets) {
356 mInsets.set(insets);
357
358 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
359 if (customScreen != null) {
360 View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0);
361 if (customContent instanceof Insettable) {
362 ((Insettable) customContent).setInsets(mInsets);
363 }
364 }
365 }
366
367 // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
368 // dimension if unsuccessful
369 public int[] estimateItemSize(int hSpan, int vSpan,
370 ItemInfo itemInfo, boolean springLoaded) {
371 int[] size = new int[2];
372 if (getChildCount() > 0) {
373 // Use the first non-custom page to estimate the child position
374 CellLayout cl = (CellLayout) getChildAt(numCustomPages());
375 Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
376 size[0] = r.width();
377 size[1] = r.height();
378 if (springLoaded) {
379 size[0] *= mSpringLoadedShrinkFactor;
380 size[1] *= mSpringLoadedShrinkFactor;
381 }
382 return size;
383 } else {
384 size[0] = Integer.MAX_VALUE;
385 size[1] = Integer.MAX_VALUE;
386 return size;
387 }
388 }
389
390 public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
391 int hCell, int vCell, int hSpan, int vSpan) {
392 Rect r = new Rect();
393 cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
394 return r;
395 }
396
397 public void onDragStart(final DragSource source, Object info, int dragAction) {
398 mIsDragOccuring = true;
399 updateChildrenLayersEnabled(false);
400 mLauncher.lockScreenOrientation();
401 mLauncher.onInteractionBegin();
402 setChildrenBackgroundAlphaMultipliers(1f);
403 // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
404 InstallShortcutReceiver.enableInstallQueue();
405 UninstallShortcutReceiver.enableUninstallQueue();
406 post(new Runnable() {
407 @Override
408 public void run() {
409 if (mIsDragOccuring) {
410 mDeferRemoveExtraEmptyScreen = false;
411 addExtraEmptyScreenOnDrag();
412 }
413 }
414 });
415 }
416
417
418 public void deferRemoveExtraEmptyScreen() {
419 mDeferRemoveExtraEmptyScreen = true;
420 }
421
422 public void onDragEnd() {
423 if (!mDeferRemoveExtraEmptyScreen) {
424 removeExtraEmptyScreen(true, mDragSourceInternal != null);
425 }
426
427 mIsDragOccuring = false;
428 updateChildrenLayersEnabled(false);
429 mLauncher.unlockScreenOrientation(false);
430
431 // Re-enable any Un/InstallShortcutReceiver and now process any queued items
432 InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
433 UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
434
435 mDragSourceInternal = null;
436 mLauncher.onInteractionEnd();
437 }
438
439 /**
440 * Initializes various states for this workspace.
441 */
442 protected void initWorkspace() {
443 mCurrentPage = mDefaultPage;
444 Launcher.setScreen(mCurrentPage);
445 LauncherAppState app = LauncherAppState.getInstance();
446 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
447 mIconCache = app.getIconCache();
448 setWillNotDraw(false);
449 setClipChildren(false);
450 setClipToPadding(false);
451 setChildrenDrawnWithCacheEnabled(true);
452
453 setMinScale(mOverviewModeShrinkFactor);
454 setupLayoutTransition();
455
456 mWallpaperOffset = new WallpaperOffsetInterpolator();
457 Display display = mLauncher.getWindowManager().getDefaultDisplay();
458 display.getSize(mDisplaySize);
459
460 mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
461 mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
462
463 // Set the wallpaper dimensions when Launcher starts up
464 setWallpaperDimension();
465 }
466
467 private void setupLayoutTransition() {
468 // We want to show layout transitions when pages are deleted, to close the gap.
469 mLayoutTransition = new LayoutTransition();
470 mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
471 mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
472 mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
473 mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
474 setLayoutTransition(mLayoutTransition);
475 }
476
477 void enableLayoutTransitions() {
478 setLayoutTransition(mLayoutTransition);
479 }
480 void disableLayoutTransitions() {
481 setLayoutTransition(null);
482 }
483
484 @Override
485 protected int getScrollMode() {
486 return SmoothPagedView.X_LARGE_MODE;
487 }
488
489 @Override
490 public void onChildViewAdded(View parent, View child) {
491 if (!(child instanceof CellLayout)) {
492 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
493 }
494 CellLayout cl = ((CellLayout) child);
495 cl.setOnInterceptTouchListener(this);
496 cl.setClickable(true);
497 cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
498 super.onChildViewAdded(parent, child);
499 }
500
501 protected boolean shouldDrawChild(View child) {
502 final CellLayout cl = (CellLayout) child;
503 return super.shouldDrawChild(child) &&
504 (mIsSwitchingState ||
505 cl.getShortcutsAndWidgets().getAlpha() > 0 ||
506 cl.getBackgroundAlpha() > 0);
507 }
508
509 /**
510 * @return The open folder on the current screen, or null if there is none
511 */
512 Folder getOpenFolder() {
513 DragLayer dragLayer = mLauncher.getDragLayer();
514 int count = dragLayer.getChildCount();
515 for (int i = 0; i < count; i++) {
516 View child = dragLayer.getChildAt(i);
517 if (child instanceof Folder) {
518 Folder folder = (Folder) child;
519 if (folder.getInfo().opened)
520 return folder;
521 }
522 }
523 return null;
524 }
525
526 boolean isTouchActive() {
527 return mTouchState != TOUCH_STATE_REST;
528 }
529
530 public void removeAllWorkspaceScreens() {
531 // Disable all layout transitions before removing all pages to ensure that we don't get the
532 // transition animations competing with us changing the scroll when we add pages or the
533 // custom content screen
534 disableLayoutTransitions();
535
536 // Since we increment the current page when we call addCustomContentPage via bindScreens
537 // (and other places), we need to adjust the current page back when we clear the pages
538 if (hasCustomContent()) {
539 removeCustomContentPage();
540 }
541
542 // Remove the pages and clear the screen models
543 removeAllViews();
544 mScreenOrder.clear();
545 mWorkspaceScreens.clear();
546
547 // Re-enable the layout transitions
548 enableLayoutTransitions();
549 }
550
551 public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
552 // Find the index to insert this view into. If the empty screen exists, then
553 // insert it before that.
554 int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
555 if (insertIndex < 0) {
556 insertIndex = mScreenOrder.size();
557 }
558 return insertNewWorkspaceScreen(screenId, insertIndex);
559 }
560
561 public long insertNewWorkspaceScreen(long screenId) {
562 return insertNewWorkspaceScreen(screenId, getChildCount());
563 }
564
565 public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
566 // Log to disk
567 Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId +
568 " at index: " + insertIndex, true);
569
570 if (mWorkspaceScreens.containsKey(screenId)) {
571 throw new RuntimeException("Screen id " + screenId + " already exists!");
572 }
573
574 CellLayout newScreen = (CellLayout)
575 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
576
577 newScreen.setOnLongClickListener(mLongClickListener);
578 newScreen.setOnClickListener(mLauncher);
579 newScreen.setSoundEffectsEnabled(false);
580 mWorkspaceScreens.put(screenId, newScreen);
581 mScreenOrder.add(insertIndex, screenId);
582 addView(newScreen, insertIndex);
583 return screenId;
584 }
585
586 public void createCustomContentContainer() {
587 CellLayout customScreen = (CellLayout)
588 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
589 customScreen.disableBackground();
590 customScreen.disableDragTarget();
591
592 mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
593 mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
594
595 // We want no padding on the custom content
596 customScreen.setPadding(0, 0, 0, 0);
597
598 addFullScreenPage(customScreen);
599
600 // Ensure that the current page and default page are maintained.
601 mDefaultPage = mOriginalDefaultPage + 1;
602
603 // Update the custom content hint
604 if (mRestorePage != INVALID_RESTORE_PAGE) {
605 mRestorePage = mRestorePage + 1;
606 } else {
607 setCurrentPage(getCurrentPage() + 1);
608 }
609 }
610
611 public void removeCustomContentPage() {
612 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
613 if (customScreen == null) {
614 throw new RuntimeException("Expected custom content screen to exist");
615 }
616
617 mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID);
618 mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID);
619 removeView(customScreen);
620
621 if (mCustomContentCallbacks != null) {
622 mCustomContentCallbacks.onScrollProgressChanged(0);
623 mCustomContentCallbacks.onHide();
624 }
625
626 mCustomContentCallbacks = null;
627
628 // Ensure that the current page and default page are maintained.
629 mDefaultPage = mOriginalDefaultPage - 1;
630
631 // Update the custom content hint
632 if (mRestorePage != INVALID_RESTORE_PAGE) {
633 mRestorePage = mRestorePage - 1;
634 } else {
635 setCurrentPage(getCurrentPage() - 1);
636 }
637 }
638
639 public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
640 String description) {
641 if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
642 throw new RuntimeException("Expected custom content screen to exist");
643 }
644
645 // Add the custom content to the full screen custom page
646 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
647 int spanX = customScreen.getCountX();
648 int spanY = customScreen.getCountY();
649 CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
650 lp.canReorder = false;
651 lp.isFullscreen = true;
652 if (customContent instanceof Insettable) {
653 ((Insettable)customContent).setInsets(mInsets);
654 }
655
656 // Verify that the child is removed from any existing parent.
657 if (customContent.getParent() instanceof ViewGroup) {
658 ViewGroup parent = (ViewGroup) customContent.getParent();
659 parent.removeView(customContent);
660 }
661 customScreen.removeAllViews();
662 customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
663 mCustomContentDescription = description;
664
665 mCustomContentCallbacks = callbacks;
666 }
667
668 public void addExtraEmptyScreenOnDrag() {
669 // Log to disk
670 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreenOnDrag()", true);
671
672 boolean lastChildOnScreen = false;
673 boolean childOnFinalScreen = false;
674
675 // Cancel any pending removal of empty screen
676 mRemoveEmptyScreenRunnable = null;
677
678 if (mDragSourceInternal != null) {
679 if (mDragSourceInternal.getChildCount() == 1) {
680 lastChildOnScreen = true;
681 }
682 CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
683 if (indexOfChild(cl) == getChildCount() - 1) {
684 childOnFinalScreen = true;
685 }
686 }
687
688 // If this is the last item on the final screen
689 if (lastChildOnScreen && childOnFinalScreen) {
690 return;
691 }
692 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
693 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
694 }
695 }
696
697 public boolean addExtraEmptyScreen() {
698 // Log to disk
699 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreen()", true);
700
701 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
702 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
703 return true;
704 }
705 return false;
706 }
707
708 private void convertFinalScreenToEmptyScreenIfNecessary() {
709 // Log to disk
710 Launcher.addDumpLog(TAG, "11683562 - convertFinalScreenToEmptyScreenIfNecessary()", true);
711
712 if (mLauncher.isWorkspaceLoading()) {
713 // Invalid and dangerous operation if workspace is loading
714 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
715 return;
716 }
717
718 if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
719 long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
720
721 if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) return;
722 CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
723
724 // If the final screen is empty, convert it to the extra empty screen
725 if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 &&
726 !finalScreen.isDropPending()) {
727 mWorkspaceScreens.remove(finalScreenId);
728 mScreenOrder.remove(finalScreenId);
729
730 // if this is the last non-custom content screen, convert it to the empty screen
731 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
732 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
733
734 // Update the model if we have changed any screens
735 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
736 Launcher.addDumpLog(TAG, "11683562 - extra empty screen: " + finalScreenId, true);
737 }
738 }
739
740 public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
741 removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
742 }
743
744 public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
745 final int delay, final boolean stripEmptyScreens) {
746 // Log to disk
747 Launcher.addDumpLog(TAG, "11683562 - removeExtraEmptyScreen()", true);
748 if (mLauncher.isWorkspaceLoading()) {
749 // Don't strip empty screens if the workspace is still loading
750 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
751 return;
752 }
753
754 if (delay > 0) {
755 postDelayed(new Runnable() {
756 @Override
757 public void run() {
758 removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
759 }
760 }, delay);
761 return;
762 }
763
764 convertFinalScreenToEmptyScreenIfNecessary();
765 if (hasExtraEmptyScreen()) {
766 int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
767 if (getNextPage() == emptyIndex) {
768 snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
769 fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
770 onComplete, stripEmptyScreens);
771 } else {
772 fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
773 onComplete, stripEmptyScreens);
774 }
775 return;
776 } else if (stripEmptyScreens) {
777 // If we're not going to strip the empty screens after removing
778 // the extra empty screen, do it right away.
779 stripEmptyScreens();
780 }
781
782 if (onComplete != null) {
783 onComplete.run();
784 }
785 }
786
787 private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
788 final boolean stripEmptyScreens) {
789 // Log to disk
790 // XXX: Do we need to update LM workspace screens below?
791 Launcher.addDumpLog(TAG, "11683562 - fadeAndRemoveEmptyScreen()", true);
792 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
793 PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
794
795 final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
796
797 mRemoveEmptyScreenRunnable = new Runnable() {
798 @Override
799 public void run() {
800 if (hasExtraEmptyScreen()) {
801 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
802 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
803 removeView(cl);
804 if (stripEmptyScreens) {
805 stripEmptyScreens();
806 }
807 }
808 }
809 };
810
811 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
812 oa.setDuration(duration);
813 oa.setStartDelay(delay);
814 oa.addListener(new AnimatorListenerAdapter() {
815 @Override
816 public void onAnimationEnd(Animator animation) {
817 if (mRemoveEmptyScreenRunnable != null) {
818 mRemoveEmptyScreenRunnable.run();
819 }
820 if (onComplete != null) {
821 onComplete.run();
822 }
823 }
824 });
825 oa.start();
826 }
827
828 public boolean hasExtraEmptyScreen() {
829 int nScreens = getChildCount();
830 nScreens = nScreens - numCustomPages();
831 return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
832 }
833
834 public long commitExtraEmptyScreen() {
835 // Log to disk
836 Launcher.addDumpLog(TAG, "11683562 - commitExtraEmptyScreen()", true);
837 if (mLauncher.isWorkspaceLoading()) {
838 // Invalid and dangerous operation if workspace is loading
839 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
840 return -1;
841 }
842
843 int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
844 CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
845 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
846 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
847
848 long newId = LauncherAppState.getLauncherProvider().generateNewScreenId();
849 mWorkspaceScreens.put(newId, cl);
850 mScreenOrder.add(newId);
851
852 // Update the page indicator marker
853 if (getPageIndicator() != null) {
854 getPageIndicator().updateMarker(index, getPageIndicatorMarker(index));
855 }
856
857 // Update the model for the new screen
858 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
859
860 return newId;
861 }
862
863 public CellLayout getScreenWithId(long screenId) {
864 CellLayout layout = mWorkspaceScreens.get(screenId);
865 return layout;
866 }
867
868 public long getIdForScreen(CellLayout layout) {
869 Iterator<Long> iter = mWorkspaceScreens.keySet().iterator();
870 while (iter.hasNext()) {
871 long id = iter.next();
872 if (mWorkspaceScreens.get(id) == layout) {
873 return id;
874 }
875 }
876 return -1;
877 }
878
879 public int getPageIndexForScreenId(long screenId) {
880 return indexOfChild(mWorkspaceScreens.get(screenId));
881 }
882
883 public long getScreenIdForPageIndex(int index) {
884 if (0 <= index && index < mScreenOrder.size()) {
885 return mScreenOrder.get(index);
886 }
887 return -1;
888 }
889
890 ArrayList<Long> getScreenOrder() {
891 return mScreenOrder;
892 }
893
894 public void stripEmptyScreens() {
895 // Log to disk
896 Launcher.addDumpLog(TAG, "11683562 - stripEmptyScreens()", true);
897
898 if (mLauncher.isWorkspaceLoading()) {
899 // Don't strip empty screens if the workspace is still loading.
900 // This is dangerous and can result in data loss.
901 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
902 return;
903 }
904
905 if (isPageMoving()) {
906 mStripScreensOnPageStopMoving = true;
907 return;
908 }
909
910 int currentPage = getNextPage();
911 ArrayList<Long> removeScreens = new ArrayList<Long>();
912 for (Long id: mWorkspaceScreens.keySet()) {
913 CellLayout cl = mWorkspaceScreens.get(id);
914 if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
915 removeScreens.add(id);
916 }
917 }
918
919 // We enforce at least one page to add new items to. In the case that we remove the last
920 // such screen, we convert the last screen to the empty screen
921 int minScreens = 1 + numCustomPages();
922
923 int pageShift = 0;
924 for (Long id: removeScreens) {
925 Launcher.addDumpLog(TAG, "11683562 - removing id: " + id, true);
926 CellLayout cl = mWorkspaceScreens.get(id);
927 mWorkspaceScreens.remove(id);
928 mScreenOrder.remove(id);
929
930 if (getChildCount() > minScreens) {
931 if (indexOfChild(cl) < currentPage) {
932 pageShift++;
933 }
934 removeView(cl);
935 } else {
936 // if this is the last non-custom content screen, convert it to the empty screen
937 mRemoveEmptyScreenRunnable = null;
938 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
939 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
940 }
941 }
942
943 if (!removeScreens.isEmpty()) {
944 // Update the model if we have changed any screens
945 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
946 }
947
948 if (pageShift >= 0) {
949 setCurrentPage(currentPage - pageShift);
950 }
951 }
952
953 // See implementation for parameter definition.
954 void addInScreen(View child, long container, long screenId,
955 int x, int y, int spanX, int spanY) {
956 addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
957 }
958
959 // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
960 // See implementation for parameter definition.
961 void addInScreenFromBind(View child, long container, long screenId, int x, int y,
962 int spanX, int spanY) {
963 addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
964 }
965
966 // See implementation for parameter definition.
967 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
968 boolean insert) {
969 addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
970 }
971
972 /**
973 * Adds the specified child in the specified screen. The position and dimension of
974 * the child are defined by x, y, spanX and spanY.
975 *
976 * @param child The child to add in one of the workspace's screens.
977 * @param screenId The screen in which to add the child.
978 * @param x The X position of the child in the screen's grid.
979 * @param y The Y position of the child in the screen's grid.
980 * @param spanX The number of cells spanned horizontally by the child.
981 * @param spanY The number of cells spanned vertically by the child.
982 * @param insert When true, the child is inserted at the beginning of the children list.
983 * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
984 * the x and y position in which to place hotseat items. Otherwise
985 * we use the x and y position to compute the rank.
986 */
987 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
988 boolean insert, boolean computeXYFromRank) {
989 if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
990 if (getScreenWithId(screenId) == null) {
991 Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
992 // DEBUGGING - Print out the stack trace to see where we are adding from
993 new Throwable().printStackTrace();
994 return;
995 }
996 }
997 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
998 // This should never happen
999 throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
1000 }
1001
1002 final CellLayout layout;
1003 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1004 layout = mLauncher.getHotseat().getLayout();
1005 child.setOnKeyListener(new HotseatIconKeyEventListener());
1006
1007 // Hide folder title in the hotseat
1008 if (child instanceof FolderIcon) {
1009 ((FolderIcon) child).setTextVisible(false);
1010 }
1011
1012 if (computeXYFromRank) {
1013 x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
1014 y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
1015 } else {
1016 screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
1017 }
1018 } else {
1019 // Show folder title if not in the hotseat
1020 if (child instanceof FolderIcon) {
1021 ((FolderIcon) child).setTextVisible(true);
1022 }
1023 layout = getScreenWithId(screenId);
1024 child.setOnKeyListener(new IconKeyEventListener());
1025 }
1026
1027 ViewGroup.LayoutParams genericLp = child.getLayoutParams();
1028 CellLayout.LayoutParams lp;
1029 if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
1030 lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
1031 } else {
1032 lp = (CellLayout.LayoutParams) genericLp;
1033 lp.cellX = x;
1034 lp.cellY = y;
1035 lp.cellHSpan = spanX;
1036 lp.cellVSpan = spanY;
1037 }
1038
1039 if (spanX < 0 && spanY < 0) {
1040 lp.isLockedToGrid = false;
1041 }
1042
1043 // Get the canonical child id to uniquely represent this view in this screen
1044 ItemInfo info = (ItemInfo) child.getTag();
1045 int childId = mLauncher.getViewIdForItem(info);
1046
1047 boolean markCellsAsOccupied = !(child instanceof Folder);
1048 if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
1049 // TODO: This branch occurs when the workspace is adding views
1050 // outside of the defined grid
1051 // maybe we should be deleting these items from the LauncherModel?
1052 Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to Cel🔵
1053 }
1054
1055 if (!(child instanceof Folder)) {
1056 child.setHapticFeedbackEnabled(false);
1057 child.setOnLongClickListener(mLongClickListener);
1058 }
1059 if (child instanceof DropTarget) {
1060 mDragController.addDropTarget((DropTarget) child);
1061 }
1062 }
1063
1064 /**
1065 * Called directly from a CellLayout (not by the framework), after we've been added as a
1066 * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
1067 * that it should intercept touch events, which is not something that is normally supported.
1068 */
1069 @Override
1070 public boolean onTouch(View v, MotionEvent event) {
1071 return (workspaceInModalState() || !isFinishedSwitchingState())
1072 || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
1073 }
1074
1075 public boolean isSwitchingState() {
1076 return mIsSwitchingState;
1077 }
1078
1079 /** This differs from isSwitchingState in that we take into account how far the transition
1080 * has completed. */
1081 public boolean isFinishedSwitchingState() {
1082 return !mIsSwitchingState || (mTransitionProgress > 0.5f);
1083 }
1084
1085 protected void onWindowVisibilityChanged (int visibility) {
1086 mLauncher.onWindowVisibilityChanged(visibility);
1087 }
1088
1089 @Override
1090 public boolean dispatchUnhandledMove(View focused, int direction) {
1091 if (workspaceInModalState() || !isFinishedSwitchingState()) {
1092 // when the home screens are shrunken, shouldn't allow side-scrolling
1093 return false;
1094 }
1095 return super.dispatchUnhandledMove(focused, direction);
1096 }
1097
1098 @Override
1099 public boolean onInterceptTouchEvent(MotionEvent ev) {
1100 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1101 case MotionEvent.ACTION_DOWN:
1102 mXDown = ev.getX();
1103 mYDown = ev.getY();
1104 mTouchDownTime = System.currentTimeMillis();
1105 break;
1106 case MotionEvent.ACTION_POINTER_UP:
1107 case MotionEvent.ACTION_UP:
1108 if (mTouchState == TOUCH_STATE_REST) {
1109 final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
1110 if (currentPage != null && !currentPage.lastDownOnOccupiedCell()) {
1111 onWallpaperTap(ev);
1112 }
1113 }
1114 }
1115 return super.onInterceptTouchEvent(ev);
1116 }
1117
1118 @Override
1119 public boolean onGenericMotionEvent(MotionEvent event) {
1120 // Ignore pointer scroll events if the custom content doesn't allow scrolling.
1121 if ((getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID)
1122 && (mCustomContentCallbacks != null)
1123 && !mCustomContentCallbacks.isScrollingAllowed()) {
1124 return false;
1125 }
1126 return super.onGenericMotionEvent(event);
1127 }
1128
1129 protected void reinflateWidgetsIfNecessary() {
1130 final int clCount = getChildCount();
1131 for (int i = 0; i < clCount; i++) {
1132 CellLayout cl = (CellLayout) getChildAt(i);
1133 ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
1134 final int itemCount = swc.getChildCount();
1135 for (int j = 0; j < itemCount; j++) {
1136 View v = swc.getChildAt(j);
1137
1138 if (v != null && v.getTag() instanceof LauncherAppWidgetInfo) {
1139 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
1140 LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
1141 if (lahv != null && lahv.isReinflateRequired()) {
1142 mLauncher.removeAppWidget(info);
1143 // Remove the current widget which is inflated with the wrong orientation
1144 cl.removeView(lahv);
1145 mLauncher.bindAppWidget(info);
1146 }
1147 }
1148 }
1149 }
1150 }
1151
1152 @Override
1153 protected void determineScrollingStart(MotionEvent ev) {
1154 if (!isFinishedSwitchingState()) return;
1155
1156 float deltaX = ev.getX() - mXDown;
1157 float absDeltaX = Math.abs(deltaX);
1158 float absDeltaY = Math.abs(ev.getY() - mYDown);
1159
1160 if (Float.compare(absDeltaX, 0f) == 0) return;
1161
1162 float slope = absDeltaY / absDeltaX;
1163 float theta = (float) Math.atan(slope);
1164
1165 if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
1166 cancelCurrentPageLongPress();
1167 }
1168
1169 boolean passRightSwipesToCustomContent =
1170 (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY;
1171
1172 boolean swipeInIgnoreDirection = isLayoutRtl() ? deltaX < 0 : deltaX > 0;
1173 boolean onCustomContentScreen =
1174 getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID;
1175 if (swipeInIgnoreDirection && onCustomContentScreen && passRightSwipesToCustomContent) {
1176 // Pass swipes to the right to the custom content page.
1177 return;
1178 }
1179
1180 if (onCustomContentScreen && (mCustomContentCallbacks != null)
1181 && !mCustomContentCallbacks.isScrollingAllowed()) {
1182 // Don't allow workspace scrolling if the current custom content screen doesn't allow
1183 // scrolling.
1184 return;
1185 }
1186
1187 if (theta > MAX_SWIPE_ANGLE) {
1188 // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
1189 return;
1190 } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
1191 // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
1192 // increase the touch slop to make it harder to begin scrolling the workspace. This
1193 // results in vertically scrolling widgets to more easily. The higher the angle, the
1194 // more we increase touch slop.
1195 theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
1196 float extraRatio = (float)
1197 Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
1198 super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
1199 } else {
1200 // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
1201 super.determineScrollingStart(ev);
1202 }
1203 }
1204
1205 protected void onPageBeginMoving() {
1206 super.onPageBeginMoving();
1207
1208 if (isHardwareAccelerated()) {
1209 updateChildrenLayersEnabled(false);
1210 } else {
1211 if (mNextPage != INVALID_PAGE) {
1212 // we're snapping to a particular screen
1213 enableChildrenCache(mCurrentPage, mNextPage);
1214 } else {
1215 // this is when user is actively dragging a particular screen, they might
1216 // swipe it either left or right (but we won't advance by more than one screen)
1217 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
1218 }
1219 }
1220 }
1221
1222 protected void onPageEndMoving() {
1223 super.onPageEndMoving();
1224
1225 if (isHardwareAccelerated()) {
1226 updateChildrenLayersEnabled(false);
1227 } else {
1228 clearChildrenCache();
1229 }
1230
1231 if (mDragController.isDragging()) {
1232 if (workspaceInModalState()) {
1233 // If we are in springloaded mode, then force an event to check if the current touch
1234 // is under a new page (to scroll to)
1235 mDragController.forceTouchMove();
1236 }
1237 }
1238
1239 if (mDelayedResizeRunnable != null) {
1240 mDelayedResizeRunnable.run();
1241 mDelayedResizeRunnable = null;
1242 }
1243
1244 if (mDelayedSnapToPageRunnable != null) {
1245 mDelayedSnapToPageRunnable.run();
1246 mDelayedSnapToPageRunnable = null;
1247 }
1248 if (mStripScreensOnPageStopMoving) {
1249 stripEmptyScreens();
1250 mStripScreensOnPageStopMoving = false;
1251 }
1252 }
1253
1254 @Override
1255 protected void notifyPageSwitchListener() {
1256 super.notifyPageSwitchListener();
1257 Launcher.setScreen(getNextPage());
1258
1259 if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
1260 mCustomContentShowing = true;
1261 if (mCustomContentCallbacks != null) {
1262 mCustomContentCallbacks.onShow(false);
1263 mCustomContentShowTime = System.currentTimeMillis();
1264 mLauncher.updateVoiceButtonProxyVisible(false);
1265 }
1266 } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
1267 mCustomContentShowing = false;
1268 if (mCustomContentCallbacks != null) {
1269 mCustomContentCallbacks.onHide();
1270 mLauncher.resetQSBScroll();
1271 mLauncher.updateVoiceButtonProxyVisible(false);
1272 }
1273 }
1274 }
1275
1276 protected CustomContentCallbacks getCustomContentCallbacks() {
1277 return mCustomContentCallbacks;
1278 }
1279
1280 protected void setWallpaperDimension() {
1281 new AsyncTask<Void, Void, Void>() {
1282 public Void doInBackground(Void ... args) {
1283 String spKey = WallpaperCropActivity.getSharedPreferencesKey();
1284 SharedPreferences sp =
1285 mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
1286 LauncherWallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
1287 sp, mLauncher.getWindowManager(), mWallpaperManager,
1288 mLauncher.overrideWallpaperDimensions());
1289 return null;
1290 }
1291 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
1292 }
1293
1294 protected void snapToPage(int whichPage, Runnable r) {
1295 snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r);
1296 }
1297
1298 protected void snapToPage(int whichPage, int duration, Runnable r) {
1299 if (mDelayedSnapToPageRunnable != null) {
1300 mDelayedSnapToPageRunnable.run();
1301 }
1302 mDelayedSnapToPageRunnable = r;
1303 snapToPage(whichPage, duration);
1304 }
1305
1306 public void snapToScreenId(long screenId) {
1307 snapToScreenId(screenId, null);
1308 }
1309
1310 protected void snapToScreenId(long screenId, Runnable r) {
1311 snapToPage(getPageIndexForScreenId(screenId), r);
1312 }
1313
1314 class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
1315 float mFinalOffset = 0.0f;
1316 float mCurrentOffset = 0.5f; // to force an initial update
1317 boolean mWaitingForUpdate;
1318 Choreographer mChoreographer;
1319 Interpolator mInterpolator;
1320 boolean mAnimating;
1321 long mAnimationStartTime;
1322 float mAnimationStartOffset;
1323 private final int ANIMATION_DURATION = 250;
1324 // Don't use all the wallpaper for parallax until you have at least this many pages
1325 private final int MIN_PARALLAX_PAGE_SPAN = 3;
1326 int mNumScreens;
1327
1328 public WallpaperOffsetInterpolator() {
1329 mChoreographer = Choreographer.getInstance();
1330 mInterpolator = new DecelerateInterpolator(1.5f);
1331 }
1332
1333 @Override
1334 public void doFrame(long frameTimeNanos) {
1335 updateOffset(false);
1336 }
1337
1338 private void updateOffset(boolean force) {
1339 if (mWaitingForUpdate || force) {
1340 mWaitingForUpdate = false;
1341 if (computeScrollOffset() && mWindowToken != null) {
1342 try {
1343 mWallpaperManager.setWallpaperOffsets(mWindowToken,
1344 mWallpaperOffset.getCurrX(), 0.5f);
1345 setWallpaperOffsetSteps();
1346 } catch (IllegalArgumentException e) {
1347 Log.e(TAG, "Error updating wallpaper offset: " + e);
1348 }
1349 }
1350 }
1351 }
1352
1353 public boolean computeScrollOffset() {
1354 final float oldOffset = mCurrentOffset;
1355 if (mAnimating) {
1356 long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
1357 float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
1358 float t1 = mInterpolator.getInterpolation(t0);
1359 mCurrentOffset = mAnimationStartOffset +
1360 (mFinalOffset - mAnimationStartOffset) * t1;
1361 mAnimating = durationSinceAnimation < ANIMATION_DURATION;
1362 } else {
1363 mCurrentOffset = mFinalOffset;
1364 }
1365
1366 if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) {
1367 scheduleUpdate();
1368 }
1369 if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) {
1370 return true;
1371 }
1372 return false;
1373 }
1374
1375 private float wallpaperOffsetForCurrentScroll() {
1376 if (getChildCount() <= 1) {
1377 return 0;
1378 }
1379
1380 // Exclude the leftmost page
1381 int emptyExtraPages = numEmptyScreensToIgnore();
1382 int firstIndex = numCustomPages();
1383 // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
1384 int lastIndex = getChildCount() - 1 - emptyExtraPages;
1385 if (isLayoutRtl()) {
1386 int temp = firstIndex;
1387 firstIndex = lastIndex;
1388 lastIndex = temp;
1389 }
1390
1391 int firstPageScrollX = getScrollForPage(firstIndex);
1392 int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX;
1393 if (scrollRange == 0) {
1394 return 0;
1395 } else {
1396 // TODO: do different behavior if it's a live wallpaper?
1397 // Sometimes the left parameter of the pages is animated during a layout transition;
1398 // this parameter offsets it to keep the wallpaper from animating as well
1399 int adjustedScroll =
1400 getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
1401 float offset = Math.min(1, adjustedScroll / (float) scrollRange);
1402 offset = Math.max(0, offset);
1403 // Don't use up all the wallpaper parallax until you have at least
1404 // MIN_PARALLAX_PAGE_SPAN pages
1405 int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
1406 int parallaxPageSpan;
1407 if (mWallpaperIsLiveWallpaper) {
1408 parallaxPageSpan = numScrollingPages - 1;
1409 } else {
1410 parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
1411 }
1412 mNumPagesForWallpaperParallax = parallaxPageSpan;
1413
1414 // On RTL devices, push the wallpaper offset to the right if we don't have enough
1415 // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
1416 int padding = isLayoutRtl() ? parallaxPageSpan - numScrollingPages + 1 : 0;
1417 return offset * (padding + numScrollingPages - 1) / parallaxPageSpan;
1418 }
1419 }
1420
1421 private int numEmptyScreensToIgnore() {
1422 int numScrollingPages = getChildCount() - numCustomPages();
1423 if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
1424 return 1;
1425 } else {
1426 return 0;
1427 }
1428 }
1429
1430 private int getNumScreensExcludingEmptyAndCustom() {
1431 int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages();
1432 return numScrollingPages;
1433 }
1434
1435 public void syncWithScroll() {
1436 float offset = wallpaperOffsetForCurrentScroll();
1437 mWallpaperOffset.setFinalX(offset);
1438 updateOffset(true);
1439 }
1440
1441 public float getCurrX() {
1442 return mCurrentOffset;
1443 }
1444
1445 public float getFinalX() {
1446 return mFinalOffset;
1447 }
1448
1449 private void animateToFinal() {
1450 mAnimating = true;
1451 mAnimationStartOffset = mCurrentOffset;
1452 mAnimationStartTime = System.currentTimeMillis();
1453 }
1454
1455 private void setWallpaperOffsetSteps() {
1456 // Set wallpaper offset steps (1 / (number of screens - 1))
1457 float xOffset = 1.0f / mNumPagesForWallpaperParallax;
1458 if (xOffset != mLastSetWallpaperOffsetSteps) {
1459 mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f);
1460 mLastSetWallpaperOffsetSteps = xOffset;
1461 }
1462 }
1463
1464 public void setFinalX(float x) {
1465 scheduleUpdate();
1466 mFinalOffset = Math.max(0f, Math.min(x, 1.0f));
1467 if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
1468 if (mNumScreens > 0) {
1469 // Don't animate if we're going from 0 screens
1470 animateToFinal();
1471 }
1472 mNumScreens = getNumScreensExcludingEmptyAndCustom();
1473 }
1474 }
1475
1476 private void scheduleUpdate() {
1477 if (!mWaitingForUpdate) {
1478 mChoreographer.postFrameCallback(this);
1479 mWaitingForUpdate = true;
1480 }
1481 }
1482
1483 public void jumpToFinal() {
1484 mCurrentOffset = mFinalOffset;
1485 }
1486 }
1487
1488 @Override
1489 public void computeScroll() {
1490 super.computeScroll();
1491 mWallpaperOffset.syncWithScroll();
1492 }
1493
1494 @Override
1495 public void announceForAccessibility(CharSequence text) {
1496 // Don't announce if apps is on top of us.
1497 if (!mLauncher.isAllAppsVisible()) {
1498 super.announceForAccessibility(text);
1499 }
1500 }
1501
1502 void showOutlines() {
1503 if (!workspaceInModalState() && !mIsSwitchingState) {
1504 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1505 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1506 mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0🔵
1507 mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
1508 mChildrenOutlineFadeInAnimation.start();
1509 }
1510 }
1511
1512 void hideOutlines() {
1513 if (!workspaceInModalState() && !mIsSwitchingState) {
1514 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1515 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1516 mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.🔵
1517 mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
1518 mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
1519 mChildrenOutlineFadeOutAnimation.start();
1520 }
1521 }
1522
1523 public void showOutlinesTemporarily() {
1524 if (!mIsPageMoving && !isTouchActive()) {
1525 snapToPage(mCurrentPage);
1526 }
1527 }
1528
1529 public void setChildrenOutlineAlpha(float alpha) {
1530 mChildrenOutlineAlpha = alpha;
1531 for (int i = 0; i < getChildCount(); i++) {
1532 CellLayout cl = (CellLayout) getChildAt(i);
1533 cl.setBackgroundAlpha(alpha);
1534 }
1535 }
1536
1537 public float getChildrenOutlineAlpha() {
1538 return mChildrenOutlineAlpha;
1539 }
1540
1541 private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1542 final DragLayer dragLayer = mLauncher.getDragLayer();
1543
1544 if (mBackgroundFadeInAnimation != null) {
1545 mBackgroundFadeInAnimation.cancel();
1546 mBackgroundFadeInAnimation = null;
1547 }
1548 if (mBackgroundFadeOutAnimation != null) {
1549 mBackgroundFadeOutAnimation.cancel();
1550 mBackgroundFadeOutAnimation = null;
1551 }
1552 float startAlpha = dragLayer.getBackgroundAlpha();
1553 if (finalAlpha != startAlpha) {
1554 if (animated) {
1555 mBackgroundFadeOutAnimation =
1556 LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
1557 mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1558 public void onAnimationUpdate(ValueAnimator animation) {
1559 dragLayer.setBackgroundAlpha(
1560 ((Float)animation.getAnimatedValue()).floatValue());
1561 }
1562 });
1563 mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
1564 mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1565 mBackgroundFadeOutAnimation.start();
1566 } else {
1567 dragLayer.setBackgroundAlpha(finalAlpha);
1568 }
1569 }
1570 }
1571
1572 float backgroundAlphaInterpolator(float r) {
1573 float pivotA = 0.1f;
1574 float pivotB = 0.4f;
1575 if (r < pivotA) {
1576 return 0;
1577 } else if (r > pivotB) {
1578 return 1.0f;
1579 } else {
1580 return (r - pivotA)/(pivotB - pivotA);
1581 }
1582 }
1583
1584 private void updatePageAlphaValues(int screenCenter) {
1585 boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1586 if (mWorkspaceFadeInAdjacentScreens &&
1587 !workspaceInModalState() &&
1588 !mIsSwitchingState &&
1589 !isInOverscroll) {
1590 for (int i = numCustomPages(); i < getChildCount(); i++) {
1591 CellLayout child = (CellLayout) getChildAt(i);
1592 if (child != null) {
1593 float scrollProgress = getScrollProgress(screenCenter, child, i);
1594 float alpha = 1 - Math.abs(scrollProgress);
1595 child.getShortcutsAndWidgets().setAlpha(alpha);
1596 //child.setBackgroundAlphaMultiplier(1 - alpha);
1597 }
1598 }
1599 }
1600 }
1601
1602 private void setChildrenBackgroundAlphaMultipliers(float a) {
1603 for (int i = 0; i < getChildCount(); i++) {
1604 CellLayout child = (CellLayout) getChildAt(i);
1605 child.setBackgroundAlphaMultiplier(a);
1606 }
1607 }
1608
1609 public boolean hasCustomContent() {
1610 return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
1611 }
1612
1613 public int numCustomPages() {
1614 return hasCustomContent() ? 1 : 0;
1615 }
1616
1617 public boolean isOnOrMovingToCustomContent() {
1618 return hasCustomContent() && getNextPage() == 0;
1619 }
1620
1621 private void updateStateForCustomContent(int screenCenter) {
1622 float translationX = 0;
1623 float progress = 0;
1624 if (hasCustomContent()) {
1625 int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
1626
1627 int scrollDelta = getScrollX() - getScrollForPage(index) -
1628 getLayoutTransitionOffsetForPage(index);
1629 float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
1630 translationX = scrollRange - scrollDelta;
1631 progress = (scrollRange - scrollDelta) / scrollRange;
1632
1633 if (isLayoutRtl()) {
1634 translationX = Math.min(0, translationX);
1635 } else {
1636 translationX = Math.max(0, translationX);
1637 }
1638 progress = Math.max(0, progress);
1639 }
1640
1641 if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return;
1642
1643 CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1644 if (progress > 0 && cc.getVisibility() != VISIBLE && !workspaceInModalState()) {
1645 cc.setVisibility(VISIBLE);
1646 }
1647
1648 mLastCustomContentScrollProgress = progress;
1649
1650 mLauncher.getDragLayer().setBackgroundAlpha(progress * 0.8f);
1651
1652 if (mLauncher.getHotseat() != null) {
1653 mLauncher.getHotseat().setTranslationX(translationX);
1654 }
1655
1656 if (getPageIndicator() != null) {
1657 getPageIndicator().setTranslationX(translationX);
1658 }
1659
1660 if (mCustomContentCallbacks != null) {
1661 mCustomContentCallbacks.onScrollProgressChanged(progress);
1662 }
1663 }
1664
1665 @Override
1666 protected OnClickListener getPageIndicatorClickListener() {
1667 AccessibilityManager am = (AccessibilityManager)
1668 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1669 if (!am.isTouchExplorationEnabled()) {
1670 return null;
1671 }
1672 OnClickListener listener = new OnClickListener() {
1673 @Override
1674 public void onClick(View arg0) {
1675 enterOverviewMode();
1676 }
1677 };
1678 return listener;
1679 }
1680
1681 @Override
1682 protected void screenScrolled(int screenCenter) {
1683 final boolean isRtl = isLayoutRtl();
1684 super.screenScrolled(screenCenter);
1685
1686 updatePageAlphaValues(screenCenter);
1687 updateStateForCustomContent(screenCenter);
1688 enableHwLayersOnVisiblePages();
1689
1690 boolean shouldOverScroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1691
1692 if (shouldOverScroll) {
1693 int index = 0;
1694 final int lowerIndex = 0;
1695 final int upperIndex = getChildCount() - 1;
1696
1697 final boolean isLeftPage = mOverScrollX < 0;
1698 index = (!isRtl && isLeftPage) || (isRtl && !isLeftPage) ? lowerIndex : upperIndex;
1699
1700 CellLayout cl = (CellLayout) getChildAt(index);
1701 float effect = Math.abs(mOverScrollEffect);
1702 cl.setOverScrollAmount(Math.abs(effect), isLeftPage);
1703
1704 mOverscrollEffectSet = true;
1705 } else {
1706 if (mOverscrollEffectSet && getChildCount() > 0) {
1707 mOverscrollEffectSet = false;
1708 ((CellLayout) getChildAt(0)).setOverScrollAmount(0, false);
1709 ((CellLayout) getChildAt(getChildCount() - 1)).setOverScrollAmount(0, false);
1710 }
1711 }
1712 }
1713
1714 @Override
1715 protected void overScroll(float amount) {
1716 boolean shouldOverScroll = (amount < 0 && (!hasCustomContent() || isLayoutRtl())) ||
1717 (amount > 0 && (!hasCustomContent() || !isLayoutRtl()));
1718 if (shouldOverScroll) {
1719 dampedOverScroll(amount);
1720 mOverScrollEffect = acceleratedOverFactor(amount);
1721 } else {
1722 mOverScrollEffect = 0;
1723 }
1724 }
1725
1726 protected void onAttachedToWindow() {
1727 super.onAttachedToWindow();
1728 mWindowToken = getWindowToken();
1729 computeScroll();
1730 mDragController.setWindowToken(mWindowToken);
1731 }
1732
1733 protected void onDetachedFromWindow() {
1734 super.onDetachedFromWindow();
1735 mWindowToken = null;
1736 }
1737
1738 protected void onResume() {
1739 if (getPageIndicator() != null) {
1740 // In case accessibility state has changed, we need to perform this on every
1741 // attach to window
1742 OnClickListener listener = getPageIndicatorClickListener();
1743 if (listener != null) {
1744 getPageIndicator().setOnClickListener(listener);
1745 }
1746 }
1747 AccessibilityManager am = (AccessibilityManager)
1748 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1749 sAccessibilityEnabled = am.isEnabled();
1750
1751 // Update wallpaper dimensions if they were changed since last onResume
1752 // (we also always set the wallpaper dimensions in the constructor)
1753 if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
1754 setWallpaperDimension();
1755 }
1756 mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null;
1757 // Force the wallpaper offset steps to be set again, because another app might have changed
1758 // them
1759 mLastSetWallpaperOffsetSteps = 0f;
1760 }
1761
1762 @Override
1763 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1764 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1765 mWallpaperOffset.syncWithScroll();
1766 mWallpaperOffset.jumpToFinal();
1767 }
1768 super.onLayout(changed, left, top, right, bottom);
1769 }
1770
1771 @Override
1772 protected void onDraw(Canvas canvas) {
1773 super.onDraw(canvas);
1774
1775 // Call back to LauncherModel to finish binding after the first draw
1776 post(mBindPages);
1777 }
1778
1779 @Override
1780 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1781 if (!mLauncher.isAllAppsVisible()) {
1782 final Folder openFolder = getOpenFolder();
1783 if (openFolder != null) {
1784 return openFolder.requestFocus(direction, previouslyFocusedRect);
1785 } else {
1786 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1787 }
1788 }
1789 return false;
1790 }
1791
1792 @Override
1793 public int getDescendantFocusability() {
1794 if (workspaceInModalState()) {
1795 return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1796 }
1797 return super.getDescendantFocusability();
1798 }
1799
1800 @Override
1801 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1802 if (!mLauncher.isAllAppsVisible()) {
1803 final Folder openFolder = getOpenFolder();
1804 if (openFolder != null) {
1805 openFolder.addFocusables(views, direction);
1806 } else {
1807 super.addFocusables(views, direction, focusableMode);
1808 }
1809 }
1810 }
1811
1812 public boolean workspaceInModalState() {
1813 return mState != State.NORMAL;
1814 }
1815
1816 void enableChildrenCache(int fromPage, int toPage) {
1817 if (fromPage > toPage) {
1818 final int temp = fromPage;
1819 fromPage = toPage;
1820 toPage = temp;
1821 }
1822
1823 final int screenCount = getChildCount();
1824
1825 fromPage = Math.max(fromPage, 0);
1826 toPage = Math.min(toPage, screenCount - 1);
1827
1828 for (int i = fromPage; i <= toPage; i++) {
1829 final CellLayout layout = (CellLayout) getChildAt(i);
1830 layout.setChildrenDrawnWithCacheEnabled(true);
1831 layout.setChildrenDrawingCacheEnabled(true);
1832 }
1833 }
1834
1835 void clearChildrenCache() {
1836 final int screenCount = getChildCount();
1837 for (int i = 0; i < screenCount; i++) {
1838 final CellLayout layout = (CellLayout) getChildAt(i);
1839 layout.setChildrenDrawnWithCacheEnabled(false);
1840 // In software mode, we don't want the items to continue to be drawn into bitmaps
1841 if (!isHardwareAccelerated()) {
1842 layout.setChildrenDrawingCacheEnabled(false);
1843 }
1844 }
1845 }
1846
1847 private void updateChildrenLayersEnabled(boolean force) {
1848 boolean small = mState == State.OVERVIEW || mIsSwitchingState;
1849 boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
1850
1851 if (enableChildrenLayers != mChildrenLayersEnabled) {
1852 mChildrenLayersEnabled = enableChildrenLayers;
1853 if (mChildrenLayersEnabled) {
1854 enableHwLayersOnVisiblePages();
1855 } else {
1856 for (int i = 0; i < getPageCount(); i++) {
1857 final CellLayout cl = (CellLayout) getChildAt(i);
1858 cl.enableHardwareLayer(false);
1859 }
1860 }
1861 }
1862 }
1863
1864 private void enableHwLayersOnVisiblePages() {
1865 if (mChildrenLayersEnabled) {
1866 final int screenCount = getChildCount();
1867 getVisiblePages(mTempVisiblePagesRange);
1868 int leftScreen = mTempVisiblePagesRange[0];
1869 int rightScreen = mTempVisiblePagesRange[1];
1870 if (leftScreen == rightScreen) {
1871 // make sure we're caching at least two pages always
1872 if (rightScreen < screenCount - 1) {
1873 rightScreen++;
1874 } else if (leftScreen > 0) {
1875 leftScreen--;
1876 }
1877 }
1878
1879 final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1880 for (int i = 0; i < screenCount; i++) {
1881 final CellLayout layout = (CellLayout) getPageAt(i);
1882
1883 // enable layers between left and right screen inclusive, except for the
1884 // customScreen, which may animate its content during transitions.
1885 boolean enableLayer = layout != customScreen &&
1886 leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
1887 layout.enableHardwareLayer(enableLayer);
1888 }
1889 }
1890 }
1891
1892 public void buildPageHardwareLayers() {
1893 // force layers to be enabled just for the call to buildLayer
1894 updateChildrenLayersEnabled(true);
1895 if (getWindowToken() != null) {
1896 final int childCount = getChildCount();
1897 for (int i = 0; i < childCount; i++) {
1898 CellLayout cl = (CellLayout) getChildAt(i);
1899 cl.buildHardwareLayer();
1900 }
1901 }
1902 updateChildrenLayersEnabled(false);
1903 }
1904
1905 protected void onWallpaperTap(MotionEvent ev) {
1906 final int[] position = mTempCell;
1907 getLocationOnScreen(position);
1908
1909 int pointerIndex = ev.getActionIndex();
1910 position[0] += (int) ev.getX(pointerIndex);
1911 position[1] += (int) ev.getY(pointerIndex);
1912
1913 mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1914 ev.getAction() == MotionEvent.ACTION_UP
1915 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1916 position[0], position[1], 0, null);
1917 }
1918
1919 /*
1920 * This interpolator emulates the rate at which the perceived scale of an object changes
1921 * as its distance from a camera increases. When this interpolator is applied to a scale
1922 * animation on a view, it evokes the sense that the object is shrinking due to moving away
1923 * from the camera.
1924 */
1925 static class ZInterpolator implements TimeInterpolator {
1926 private float focalLength;
1927
1928 public ZInterpolator(float foc) {
1929 focalLength = foc;
1930 }
1931
1932 public float getInterpolation(float input) {
1933 return (1.0f - focalLength / (focalLength + input)) /
1934 (1.0f - focalLength / (focalLength + 1.0f));
1935 }
1936 }
1937
1938 /*
1939 * The exact reverse of ZInterpolator.
1940 */
1941 static class InverseZInterpolator implements TimeInterpolator {
1942 private ZInterpolator zInterpolator;
1943 public InverseZInterpolator(float foc) {
1944 zInterpolator = new ZInterpolator(foc);
1945 }
1946 public float getInterpolation(float input) {
1947 return 1 - zInterpolator.getInterpolation(1 - input);
1948 }
1949 }
1950
1951 /*
1952 * ZInterpolator compounded with an ease-out.
1953 */
1954 static class ZoomOutInterpolator implements TimeInterpolator {
1955 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
1956 private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
1957
1958 public float getInterpolation(float input) {
1959 return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1960 }
1961 }
1962
1963 /*
1964 * InvereZInterpolator compounded with an ease-out.
1965 */
1966 static class ZoomInInterpolator implements TimeInterpolator {
1967 private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
1968 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
1969
1970 public float getInterpolation(float input) {
1971 return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1972 }
1973 }
1974
1975 private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1976
1977 /*
1978 *
1979 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1980 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1981 *
1982 * These methods mark the appropriate pages as accepting drops (which alters their visual
1983 * appearance).
1984 *
1985 */
1986 private static Rect getDrawableBounds(Drawable d) {
1987 Rect bounds = new Rect();
1988 d.copyBounds(bounds);
1989 if (bounds.width() == 0 || bounds.height() == 0) {
1990 bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
1991 } else {
1992 bounds.offsetTo(0, 0);
1993 }
1994 if (d instanceof PreloadIconDrawable) {
1995 int inset = -((PreloadIconDrawable) d).getOutset();
1996 bounds.inset(inset, inset);
1997 }
1998 return bounds;
1999 }
2000
2001 public void onExternalDragStartedWithItem(View v) {
2002 // Compose a drag bitmap with the view scaled to the icon size
2003 LauncherAppState app = LauncherAppState.getInstance();
2004 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2005 int iconSize = grid.iconSizePx;
2006 int bmpWidth = v.getMeasuredWidth();
2007 int bmpHeight = v.getMeasuredHeight();
2008
2009 // If this is a text view, use its drawable instead
2010 if (v instanceof TextView) {
2011 TextView tv = (TextView) v;
2012 Drawable d = tv.getCompoundDrawables()[1];
2013 Rect bounds = getDrawableBounds(d);
2014 bmpWidth = bounds.width();
2015 bmpHeight = bounds.height();
2016 }
2017
2018 // Compose the bitmap to create the icon from
2019 Bitmap b = Bitmap.createBitmap(bmpWidth, bmpHeight,
2020 Bitmap.Config.ARGB_8888);
2021 mCanvas.setBitmap(b);
2022 drawDragView(v, mCanvas, 0);
2023 mCanvas.setBitmap(null);
2024
2025 // The outline is used to visualize where the item will land if dropped
2026 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, iconSize, iconSize, true);
2027 }
2028
2029 public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
2030 int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
2031
2032 // The outline is used to visualize where the item will land if dropped
2033 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
2034 }
2035
2036 public void exitWidgetResizeMode() {
2037 DragLayer dragLayer = mLauncher.getDragLayer();
2038 dragLayer.clearAllResizeFrames();
2039 }
2040
2041 private void initAnimationArrays() {
2042 final int childCount = getChildCount();
2043 if (mLastChildCount == childCount) return;
2044
2045 mOldBackgroundAlphas = new float[childCount];
2046 mOldAlphas = new float[childCount];
2047 mNewBackgroundAlphas = new float[childCount];
2048 mNewAlphas = new float[childCount];
2049 }
2050
2051 Animator getChangeStateAnimation(final State state, boolean animated,
2052 ArrayList<View> layerViews) {
2053 return getChangeStateAnimation(state, animated, 0, -1, layerViews);
2054 }
2055
2056 @Override
2057 protected void getFreeScrollPageRange(int[] range) {
2058 getOverviewModePages(range);
2059 }
2060
2061 private void getOverviewModePages(int[] range) {
2062 int start = numCustomPages();
2063 int end = getChildCount() - 1;
2064
2065 range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
2066 range[1] = Math.max(0, end);
2067 }
2068
2069 protected void onStartReordering() {
2070 super.onStartReordering();
2071 showOutlines();
2072 // Reordering handles its own animations, disable the automatic ones.
2073 disableLayoutTransitions();
2074 }
2075
2076 protected void onEndReordering() {
2077 super.onEndReordering();
2078
2079 if (mLauncher.isWorkspaceLoading()) {
2080 // Invalid and dangerous operation if workspace is loading
2081 return;
2082 }
2083
2084 hideOutlines();
2085 mScreenOrder.clear();
2086 int count = getChildCount();
2087 for (int i = 0; i < count; i++) {
2088 CellLayout cl = ((CellLayout) getChildAt(i));
2089 mScreenOrder.add(getIdForScreen(cl));
2090 }
2091
2092 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
2093
2094 // Re-enable auto layout transitions for page deletion.
2095 enableLayoutTransitions();
2096 }
2097
2098 public boolean isInOverviewMode() {
2099 return mState == State.OVERVIEW;
2100 }
2101
2102 public boolean enterOverviewMode() {
2103 if (mTouchState != TOUCH_STATE_REST) {
2104 return false;
2105 }
2106 enableOverviewMode(true, -1, true);
2107 return true;
2108 }
2109
2110 public void exitOverviewMode(boolean animated) {
2111 exitOverviewMode(-1, animated);
2112 }
2113
2114 public void exitOverviewMode(int snapPage, boolean animated) {
2115 enableOverviewMode(false, snapPage, animated);
2116 }
2117
2118 private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
2119 State finalState = Workspace.State.OVERVIEW;
2120 if (!enable) {
2121 finalState = Workspace.State.NORMAL;
2122 }
2123
2124 Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
2125 if (workspaceAnim != null) {
2126 onTransitionPrepare();
2127 workspaceAnim.addListener(new AnimatorListenerAdapter() {
2128 @Override
2129 public void onAnimationEnd(Animator arg0) {
2130 onTransitionEnd();
2131 }
2132 });
2133 workspaceAnim.start();
2134 }
2135 }
2136
2137 int getOverviewModeTranslationY() {
2138 LauncherAppState app = LauncherAppState.getInstance();
2139 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2140 Rect overviewBar = grid.getOverviewModeButtonBarRect();
2141
2142 int availableHeight = getViewportHeight();
2143 int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
2144 int offsetFromTopEdge = (availableHeight - scaledHeight) / 2;
2145 int offsetToCenterInOverview = (availableHeight - mInsets.top - overviewBar.height()
2146 - scaledHeight) / 2;
2147
2148 return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
2149 }
2150
2151 boolean shouldVoiceButtonProxyBeVisible() {
2152 if (isOnOrMovingToCustomContent()) {
2153 return false;
2154 }
2155 if (mState != State.NORMAL) {
2156 return false;
2157 }
2158 return true;
2159 }
2160
2161 public void updateInteractionForState() {
2162 if (mState != State.NORMAL) {
2163 mLauncher.onInteractionBegin();
2164 } else {
2165 mLauncher.onInteractionEnd();
2166 }
2167 }
2168
2169 private void setState(State state) {
2170 mState = state;
2171 updateInteractionForState();
2172 updateAccessibilityFlags();
2173 }
2174
2175 State getState() {
2176 return mState;
2177 }
2178
2179 private void updateAccessibilityFlags() {
2180 int accessible = mState == State.NORMAL ?
2181 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
2182 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
2183 setImportantForAccessibility(accessible);
2184 }
2185
2186 private static final int HIDE_WORKSPACE_DURATION = 100;
2187
2188 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
2189 return getChangeStateAnimation(state, animated, delay, snapPage, null);
2190 }
2191
2192 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage,
2193 ArrayList<View> layerViews) {
2194 if (mState == state) {
2195 return null;
2196 }
2197
2198 // Initialize animation arrays for the first time if necessary
2199 initAnimationArrays();
2200
2201 AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
2202
2203 final State oldState = mState;
2204 final boolean oldStateIsNormal = (oldState == State.NORMAL);
2205 final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
2206 final boolean oldStateIsNormalHidden = (oldState == State.NORMAL_HIDDEN);
2207 final boolean oldStateIsOverviewHidden = (oldState == State.OVERVIEW_HIDDEN);
2208 final boolean oldStateIsOverview = (oldState == State.OVERVIEW);
2209 setState(state);
2210 final boolean stateIsNormal = (state == State.NORMAL);
2211 final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
2212 final boolean stateIsNormalHidden = (state == State.NORMAL_HIDDEN);
2213 final boolean stateIsOverviewHidden = (state == State.OVERVIEW_HIDDEN);
2214 final boolean stateIsOverview = (state == State.OVERVIEW);
2215 float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
2216 float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1f : 0f;
2217 float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
2218 float finalSearchBarAlpha = !stateIsNormal ? 0f : 1f;
2219 float finalWorkspaceTranslationY = stateIsOverview || stateIsOverviewHidden ?
2220 getOverviewModeTranslationY() : 0;
2221
2222 boolean workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
2223 boolean overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
2224 boolean allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal);
2225 boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
2226 boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
2227
2228 mNewScale = 1.0f;
2229
2230 if (oldStateIsOverview) {
2231 disableFreeScroll();
2232 } else if (stateIsOverview) {
2233 enableFreeScroll();
2234 }
2235
2236 if (state != State.NORMAL) {
2237 if (stateIsSpringLoaded) {
2238 mNewScale = mSpringLoadedShrinkFactor;
2239 } else if (stateIsOverview || stateIsOverviewHidden) {
2240 mNewScale = mOverviewModeShrinkFactor;
2241 }
2242 }
2243
2244 final int duration;
2245 if (workspaceToAllApps || overviewToAllApps) {
2246 duration = HIDE_WORKSPACE_DURATION; //getResources().getInteger(R.integer.config_workspaceUns🔵
2247 } else if (workspaceToOverview || overviewToWorkspace) {
2248 duration = getResources().getInteger(R.integer.config_overviewTransitionTime);
2249 } else {
2250 duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
2251 }
2252
2253 if (snapPage == -1) {
2254 snapPage = getPageNearestToCenterOfScreen();
2255 }
2256 snapToPage(snapPage, duration, mZoomInInterpolator);
2257
2258 for (int i = 0; i < getChildCount(); i++) {
2259 final CellLayout cl = (CellLayout) getChildAt(i);
2260 boolean isCurrentPage = (i == snapPage);
2261 float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
2262 float finalAlpha;
2263 if (stateIsNormalHidden || stateIsOverviewHidden) {
2264 finalAlpha = 0f;
2265 } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
2266 finalAlpha = (i == snapPage || i < numCustomPages()) ? 1f : 0f;
2267 } else {
2268 finalAlpha = 1f;
2269 }
2270
2271 // If we are animating to/from the small state, then hide the side pages and fade the
2272 // current page in
2273 if (!mIsSwitchingState) {
2274 if (workspaceToAllApps || allAppsToWorkspace) {
2275 if (allAppsToWorkspace && isCurrentPage) {
2276 initialAlpha = 0f;
2277 } else if (!isCurrentPage) {
2278 initialAlpha = finalAlpha = 0f;
2279 }
2280 cl.setShortcutAndWidgetAlpha(initialAlpha);
2281 }
2282 }
2283
2284 mOldAlphas[i] = initialAlpha;
2285 mNewAlphas[i] = finalAlpha;
2286 if (animated) {
2287 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
2288 mNewBackgroundAlphas[i] = finalBackgroundAlpha;
2289 } else {
2290 cl.setBackgroundAlpha(finalBackgroundAlpha);
2291 cl.setShortcutAndWidgetAlpha(finalAlpha);
2292 }
2293 }
2294
2295 final View searchBar = mLauncher.getQsbBar();
2296 final View overviewPanel = mLauncher.getOverviewPanel();
2297 final View hotseat = mLauncher.getHotseat();
2298 final View pageIndicator = getPageIndicator();
2299 if (animated) {
2300 LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
2301 scale.scaleX(mNewScale)
2302 .scaleY(mNewScale)
2303 .translationY(finalWorkspaceTranslationY)
2304 .setDuration(duration)
2305 .setInterpolator(mZoomInInterpolator);
2306 anim.play(scale);
2307 for (int index = 0; index < getChildCount(); index++) {
2308 final int i = index;
2309 final CellLayout cl = (CellLayout) getChildAt(i);
2310 float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
2311 if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
2312 cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
2313 cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
2314 } else {
2315 if (layerViews != null) {
2316 layerViews.add(cl);
2317 }
2318 if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
2319 LauncherViewPropertyAnimator alphaAnim =
2320 new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
2321 alphaAnim.alpha(mNewAlphas[i])
2322 .setDuration(duration)
2323 .setInterpolator(mZoomInInterpolator);
2324 anim.play(alphaAnim);
2325 }
2326 if (mOldBackgroundAlphas[i] != 0 ||
2327 mNewBackgroundAlphas[i] != 0) {
2328 ValueAnimator bgAnim =
2329 LauncherAnimUtils.ofFloat(cl, 0f, 1f);
2330 bgAnim.setInterpolator(mZoomInInterpolator);
2331 bgAnim.setDuration(duration);
2332 bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2333 public void onAnimationUpdate(float a, float b) {
2334 cl.setBackgroundAlpha(
2335 a * mOldBackgroundAlphas[i] +
2336 b * mNewBackgroundAlphas[i]);
2337 }
2338 });
2339 anim.play(bgAnim);
2340 }
2341 }
2342 }
2343 Animator pageIndicatorAlpha = null;
2344 if (pageIndicator != null) {
2345 pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
2346 .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
2347 pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator));
2348 } else {
2349 // create a dummy animation so we don't need to do null checks later
2350 pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
2351 }
2352
2353 Animator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat)
2354 .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
2355 hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
2356
2357 Animator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
2358 .alpha(finalSearchBarAlpha).withLayer();
2359 searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
2360
2361 Animator overviewPanelAlpha = new LauncherViewPropertyAnimator(overviewPanel)
2362 .alpha(finalOverviewPanelAlpha).withLayer();
2363 overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
2364
2365 // For animation optimations, we may need to provide the Launcher transition
2366 // with a set of views on which to force build layers in certain scenarios.
2367 hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2368 searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2369 overviewPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2370 if (layerViews != null) {
2371 layerViews.add(hotseat);
2372 layerViews.add(searchBar);
2373 layerViews.add(overviewPanel);
2374 }
2375
2376 if (workspaceToOverview) {
2377 pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
2378 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
2379 overviewPanelAlpha.setInterpolator(null);
2380 } else if (overviewToWorkspace) {
2381 pageIndicatorAlpha.setInterpolator(null);
2382 hotseatAlpha.setInterpolator(null);
2383 overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
2384 }
2385
2386 overviewPanelAlpha.setDuration(duration);
2387 pageIndicatorAlpha.setDuration(duration);
2388 hotseatAlpha.setDuration(duration);
2389 searchBarAlpha.setDuration(duration);
2390
2391 anim.play(overviewPanelAlpha);
2392 anim.play(hotseatAlpha);
2393 anim.play(searchBarAlpha);
2394 anim.play(pageIndicatorAlpha);
2395 anim.setStartDelay(delay);
2396 } else {
2397 overviewPanel.setAlpha(finalOverviewPanelAlpha);
2398 AlphaUpdateListener.updateVisibility(overviewPanel);
2399 hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
2400 AlphaUpdateListener.updateVisibility(hotseat);
2401 if (pageIndicator != null) {
2402 pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
2403 AlphaUpdateListener.updateVisibility(pageIndicator);
2404 }
2405 searchBar.setAlpha(finalSearchBarAlpha);
2406 AlphaUpdateListener.updateVisibility(searchBar);
2407 updateCustomContentVisibility();
2408 setScaleX(mNewScale);
2409 setScaleY(mNewScale);
2410 setTranslationY(finalWorkspaceTranslationY);
2411 }
2412 mLauncher.updateVoiceButtonProxyVisible(false);
2413
2414 if (stateIsNormal) {
2415 animateBackgroundGradient(0f, animated);
2416 } else {
2417 animateBackgroundGradient(getResources().getInteger(
2418 R.integer.config_workspaceScrimAlpha) / 100f, animated);
2419 }
2420 return anim;
2421 }
2422
2423 static class AlphaUpdateListener implements AnimatorUpdateListener, AnimatorListener {
2424 View view;
2425 public AlphaUpdateListener(View v) {
2426 view = v;
2427 }
2428
2429 @Override
2430 public void onAnimationUpdate(ValueAnimator arg0) {
2431 updateVisibility(view);
2432 }
2433
2434 public static void updateVisibility(View view) {
2435 // We want to avoid the extra layout pass by setting the views to GONE unless
2436 // accessibility is on, in which case not setting them to GONE causes a glitch.
2437 int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE;
2438 if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
2439 view.setVisibility(invisibleState);
2440 } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
2441 && view.getVisibility() != VISIBLE) {
2442 view.setVisibility(VISIBLE);
2443 }
2444 }
2445
2446 @Override
2447 public void onAnimationCancel(Animator arg0) {
2448 }
2449
2450 @Override
2451 public void onAnimationEnd(Animator arg0) {
2452 updateVisibility(view);
2453 }
2454
2455 @Override
2456 public void onAnimationRepeat(Animator arg0) {
2457 }
2458
2459 @Override
2460 public void onAnimationStart(Animator arg0) {
2461 // We want the views to be visible for animation, so fade-in/out is visible
2462 view.setVisibility(VISIBLE);
2463 }
2464 }
2465
2466 @Override
2467 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
2468 onTransitionPrepare();
2469 }
2470
2471 @Override
2472 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
2473 }
2474
2475 @Override
2476 public void onLauncherTransitionStep(Launcher l, float t) {
2477 mTransitionProgress = t;
2478 }
2479
2480 @Override
2481 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
2482 onTransitionEnd();
2483 }
2484
2485 private void onTransitionPrepare() {
2486 mIsSwitchingState = true;
2487
2488 // Invalidate here to ensure that the pages are rendered during the state change transition.
2489 invalidate();
2490
2491 updateChildrenLayersEnabled(false);
2492 hideCustomContentIfNecessary();
2493 }
2494
2495 void updateCustomContentVisibility() {
2496 int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
2497 if (hasCustomContent()) {
2498 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
2499 }
2500 }
2501
2502 void showCustomContentIfNecessary() {
2503 boolean show = mState == Workspace.State.NORMAL;
2504 if (show && hasCustomContent()) {
2505 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2506 }
2507 }
2508
2509 void hideCustomContentIfNecessary() {
2510 boolean hide = mState != Workspace.State.NORMAL;
2511 if (hide && hasCustomContent()) {
2512 disableLayoutTransitions();
2513 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
2514 enableLayoutTransitions();
2515 }
2516 }
2517
2518 private void onTransitionEnd() {
2519 mIsSwitchingState = false;
2520 updateChildrenLayersEnabled(false);
2521 showCustomContentIfNecessary();
2522 }
2523
2524 @Override
2525 public View getContent() {
2526 return this;
2527 }
2528
2529 /**
2530 * Draw the View v into the given Canvas.
2531 *
2532 * @param v the view to draw
2533 * @param destCanvas the canvas to draw on
2534 * @param padding the horizontal and vertical padding to use when drawing
2535 */
2536 private static void drawDragView(View v, Canvas destCanvas, int padding) {
2537 final Rect clipRect = sTempRect;
2538 v.getDrawingRect(clipRect);
2539
2540 boolean textVisible = false;
2541
2542 destCanvas.save();
2543 if (v instanceof TextView) {
2544 Drawable d = ((TextView) v).getCompoundDrawables()[1];
2545 Rect bounds = getDrawableBounds(d);
2546 clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding);
2547 destCanvas.translate(padding / 2 - bounds.left, padding / 2 - bounds.top);
2548 d.draw(destCanvas);
2549 } else {
2550 if (v instanceof FolderIcon) {
2551 // For FolderIcons the text can bleed into the icon area, and so we need to
2552 // hide the text completely (which can't be achieved by clipping).
2553 if (((FolderIcon) v).getTextVisible()) {
2554 ((FolderIcon) v).setTextVisible(false);
2555 textVisible = true;
2556 }
2557 }
2558 destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
2559 destCanvas.clipRect(clipRect, Op.REPLACE);
2560 v.draw(destCanvas);
2561
2562 // Restore text visibility of FolderIcon if necessary
2563 if (textVisible) {
2564 ((FolderIcon) v).setTextVisible(true);
2565 }
2566 }
2567 destCanvas.restore();
2568 }
2569
2570 /**
2571 * Returns a new bitmap to show when the given View is being dragged around.
2572 * Responsibility for the bitmap is transferred to the caller.
2573 * @param expectedPadding padding to add to the drag view. If a different padding was used
2574 * its value will be changed
2575 */
2576 public Bitmap createDragBitmap(View v, AtomicInteger expectedPadding) {
2577 Bitmap b;
2578
2579 int padding = expectedPadding.get();
2580 if (v instanceof TextView) {
2581 Drawable d = ((TextView) v).getCompoundDrawables()[1];
2582 Rect bounds = getDrawableBounds(d);
2583 b = Bitmap.createBitmap(bounds.width() + padding,
2584 bounds.height() + padding, Bitmap.Config.ARGB_8888);
2585 expectedPadding.set(padding - bounds.left - bounds.top);
2586 } else {
2587 b = Bitmap.createBitmap(
2588 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2589 }
2590
2591 mCanvas.setBitmap(b);
2592 drawDragView(v, mCanvas, padding);
2593 mCanvas.setBitmap(null);
2594
2595 return b;
2596 }
2597
2598 /**
2599 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2600 * Responsibility for the bitmap is transferred to the caller.
2601 */
2602 private Bitmap createDragOutline(View v, int padding) {
2603 final int outlineColor = getResources().getColor(R.color.outline_color);
2604 final Bitmap b = Bitmap.createBitmap(
2605 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2606
2607 mCanvas.setBitmap(b);
2608 drawDragView(v, mCanvas, padding);
2609 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor);
2610 mCanvas.setBitmap(null);
2611 return b;
2612 }
2613
2614 /**
2615 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2616 * Responsibility for the bitmap is transferred to the caller.
2617 */
2618 private Bitmap createDragOutline(Bitmap orig, int padding, int w, int h,
2619 boolean clipAlpha) {
2620 final int outlineColor = getResources().getColor(R.color.outline_color);
2621 final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
2622 mCanvas.setBitmap(b);
2623
2624 Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
2625 float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
2626 (h - padding) / (float) orig.getHeight());
2627 int scaledWidth = (int) (scaleFactor * orig.getWidth());
2628 int scaledHeight = (int) (scaleFactor * orig.getHeight());
2629 Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2630
2631 // center the image
2632 dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2633
2634 mCanvas.drawBitmap(orig, src, dst, null);
2635 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor,
2636 clipAlpha);
2637 mCanvas.setBitmap(null);
2638
2639 return b;
2640 }
2641
2642 void startDrag(CellLayout.CellInfo cellInfo) {
2643 View child = cellInfo.cell;
2644
2645 // Make sure the drag was started by a long press as opposed to a long click.
2646 if (!child.isInTouchMode()) {
2647 return;
2648 }
2649
2650 mDragInfo = cellInfo;
2651 child.setVisibility(INVISIBLE);
2652 CellLayout layout = (CellLayout) child.getParent().getParent();
2653 layout.prepareChildForDrag(child);
2654
2655 beginDragShared(child, this);
2656 }
2657
2658 public void beginDragShared(View child, DragSource source) {
2659 child.clearFocus();
2660 child.setPressed(false);
2661
2662 // The outline is used to visualize where the item will land if dropped
2663 mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING);
2664
2665 mLauncher.onDragStarted(child);
2666 // The drag bitmap follows the touch point around on the screen
2667 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2668 final Bitmap b = createDragBitmap(child, padding);
2669
2670 final int bmpWidth = b.getWidth();
2671 final int bmpHeight = b.getHeight();
2672
2673 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2674 int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2675 int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
2676 - padding.get() / 2);
2677
2678 LauncherAppState app = LauncherAppState.getInstance();
2679 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2680 Point dragVisualizeOffset = null;
2681 Rect dragRect = null;
2682 if (child instanceof BubbleTextView) {
2683 int iconSize = grid.iconSizePx;
2684 int top = child.getPaddingTop();
2685 int left = (bmpWidth - iconSize) / 2;
2686 int right = left + iconSize;
2687 int bottom = top + iconSize;
2688 dragLayerY += top;
2689 // Note: The drag region is used to calculate drag layer offsets, but the
2690 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2691 dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
2692 dragRect = new Rect(left, top, right, bottom);
2693 } else if (child instanceof FolderIcon) {
2694 int previewSize = grid.folderIconSizePx;
2695 dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2696 }
2697
2698 // Clear the pressed state if necessary
2699 if (child instanceof BubbleTextView) {
2700 BubbleTextView icon = (BubbleTextView) child;
2701 icon.clearPressedBackground();
2702 }
2703
2704 if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
2705 String msg = "Drag started with a view that has no tag set. This "
2706 + "will cause a crash (issue 11627249) down the line. "
2707 + "View: " + child + " tag: " + child.getTag();
2708 throw new IllegalStateException(msg);
2709 }
2710
2711 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2712 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2713 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2714
2715 if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2716 mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
2717 }
2718
2719 b.recycle();
2720 }
2721
2722 public void beginExternalDragShared(View child, DragSource source) {
2723 LauncherAppState app = LauncherAppState.getInstance();
2724 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2725 int iconSize = grid.iconSizePx;
2726
2727 // Notify launcher of drag start
2728 mLauncher.onDragStarted(child);
2729
2730 // Compose a new drag bitmap that is of the icon size
2731 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2732 final Bitmap tmpB = createDragBitmap(child, padding);
2733 Bitmap b = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
2734 Paint p = new Paint();
2735 p.setFilterBitmap(true);
2736 mCanvas.setBitmap(b);
2737 mCanvas.drawBitmap(tmpB, new Rect(0, 0, tmpB.getWidth(), tmpB.getHeight()),
2738 new Rect(0, 0, iconSize, iconSize), p);
2739 mCanvas.setBitmap(null);
2740
2741 // Find the child's location on the screen
2742 int bmpWidth = tmpB.getWidth();
2743 float iconScale = (float) bmpWidth / iconSize;
2744 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY) * iconScale;
2745 int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2746 int dragLayerY = Math.round(mTempXY[1]);
2747
2748 // Note: The drag region is used to calculate drag layer offsets, but the
2749 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2750 Point dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
2751 Rect dragRect = new Rect(0, 0, iconSize, iconSize);
2752
2753 if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
2754 String msg = "Drag started with a view that has no tag set. This "
2755 + "will cause a crash (issue 11627249) down the line. "
2756 + "View: " + child + " tag: " + child.getTag();
2757 throw new IllegalStateException(msg);
2758 }
2759
2760 // Start the drag
2761 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2762 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2763 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2764
2765 // Recycle temporary bitmaps
2766 tmpB.recycle();
2767 }
2768
2769 void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2770 int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2771 View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2772
2773 final int[] cellXY = new int[2];
2774 target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2775 addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2776
2777 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2778 cellXY[1]);
2779 }
2780
2781 public boolean transitionStateShouldAllowDrop() {
2782 return ((!isSwitchingState() || mTransitionProgress > 0.5f) &&
2783 (mState == State.NORMAL || mState == State.SPRING_LOADED));
2784 }
2785
2786 /**
2787 * {@inheritDoc}
2788 */
2789 public boolean acceptDrop(DragObject d) {
2790 // If it's an external drop (e.g. from All Apps), check if it should be accepted
2791 CellLayout dropTargetLayout = mDropToLayout;
2792 if (d.dragSource != this) {
2793 // Don't accept the drop if we're not over a screen at time of drop
2794 if (dropTargetLayout == null) {
2795 return false;
2796 }
2797 if (!transitionStateShouldAllowDrop()) return false;
2798
2799 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2800 d.dragView, mDragViewVisualCenter);
2801
2802 // We want the point to be mapped to the dragTarget.
2803 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2804 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2805 } else {
2806 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2807 }
2808
2809 int spanX = 1;
2810 int spanY = 1;
2811 if (mDragInfo != null) {
2812 final CellLayout.CellInfo dragCellInfo = mDragInfo;
2813 spanX = dragCellInfo.spanX;
2814 spanY = dragCellInfo.spanY;
2815 } else {
2816 final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
2817 spanX = dragInfo.spanX;
2818 spanY = dragInfo.spanY;
2819 }
2820
2821 int minSpanX = spanX;
2822 int minSpanY = spanY;
2823 if (d.dragInfo instanceof PendingAddWidgetInfo) {
2824 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2825 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2826 }
2827
2828 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2829 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2830 mTargetCell);
2831 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2832 mDragViewVisualCenter[1], mTargetCell);
2833 if (mCreateUserFolderOnDrop && willCreateUserFolder((ItemInfo) d.dragInfo,
2834 dropTargetLayout, mTargetCell, distance, true)) {
2835 return true;
2836 }
2837
2838 if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder((ItemInfo) d.dragInfo,
2839 dropTargetLayout, mTargetCell, distance)) {
2840 return true;
2841 }
2842
2843 int[] resultSpan = new int[2];
2844 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2845 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2846 null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2847 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2848
2849 // Don't accept the drop if there's no room for the item
2850 if (!foundCell) {
2851 // Don't show the message if we are dropping on the AllApps button and the hotseat
2852 // is full
2853 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2854 if (mTargetCell != null && isHotseat) {
2855 Hotseat hotseat = mLauncher.getHotseat();
2856 if (hotseat.isAllAppsButtonRank(
2857 hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2858 return false;
2859 }
2860 }
2861
2862 mLauncher.showOutOfSpaceMessage(isHotseat);
2863 return false;
2864 }
2865 }
2866
2867 long screenId = getIdForScreen(dropTargetLayout);
2868 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2869 commitExtraEmptyScreen();
2870 }
2871
2872 return true;
2873 }
2874
2875 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2876 distance, boolean considerTimeout) {
2877 if (distance > mMaxDistanceForFolderCreation) return false;
2878 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2879
2880 if (dropOverView != null) {
2881 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2882 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2883 return false;
2884 }
2885 }
2886
2887 boolean hasntMoved = false;
2888 if (mDragInfo != null) {
2889 hasntMoved = dropOverView == mDragInfo.cell;
2890 }
2891
2892 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2893 return false;
2894 }
2895
2896 boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2897 boolean willBecomeShortcut =
2898 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2899 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2900
2901 return (aboveShortcut && willBecomeShortcut);
2902 }
2903
2904 boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2905 float distance) {
2906 if (distance > mMaxDistanceForFolderCreation) return false;
2907 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2908
2909 if (dropOverView != null) {
2910 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2911 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2912 return false;
2913 }
2914 }
2915
2916 if (dropOverView instanceof FolderIcon) {
2917 FolderIcon fi = (FolderIcon) dropOverView;
2918 if (fi.acceptDrop(dragInfo)) {
2919 return true;
2920 }
2921 }
2922 return false;
2923 }
2924
2925 boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2926 int[] targetCell, float distance, boolean external, DragView dragView,
2927 Runnable postAnimationRunnable) {
2928 if (distance > mMaxDistanceForFolderCreation) return false;
2929 View v = target.getChildAt(targetCell[0], targetCell[1]);
2930
2931 boolean hasntMoved = false;
2932 if (mDragInfo != null) {
2933 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2934 hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2935 mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2936 }
2937
2938 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2939 mCreateUserFolderOnDrop = false;
2940 final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
2941
2942 boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2943 boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2944
2945 if (aboveShortcut && willBecomeShortcut) {
2946 ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2947 ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2948 // if the drag started here, we need to remove it from the workspace
2949 if (!external) {
2950 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2951 }
2952
2953 Rect folderLocation = new Rect();
2954 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2955 target.removeView(v);
2956
2957 FolderIcon fi =
2958 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
2959 destInfo.cellX = -1;
2960 destInfo.cellY = -1;
2961 sourceInfo.cellX = -1;
2962 sourceInfo.cellY = -1;
2963
2964 // If the dragView is null, we can't animate
2965 boolean animate = dragView != null;
2966 if (animate) {
2967 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2968 postAnimationRunnable);
2969 } else {
2970 fi.addItem(destInfo);
2971 fi.addItem(sourceInfo);
2972 }
2973 return true;
2974 }
2975 return false;
2976 }
2977
2978 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2979 float distance, DragObject d, boolean external) {
2980 if (distance > mMaxDistanceForFolderCreation) return false;
2981
2982 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2983 if (!mAddToExistingFolderOnDrop) return false;
2984 mAddToExistingFolderOnDrop = false;
2985
2986 if (dropOverView instanceof FolderIcon) {
2987 FolderIcon fi = (FolderIcon) dropOverView;
2988 if (fi.acceptDrop(d.dragInfo)) {
2989 fi.onDrop(d);
2990
2991 // if the drag started here, we need to remove it from the workspace
2992 if (!external) {
2993 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2994 }
2995 return true;
2996 }
2997 }
2998 return false;
2999 }
3000
3001 public void onDrop(final DragObject d) {
3002 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
3003 mDragViewVisualCenter);
3004
3005 CellLayout dropTargetLayout = mDropToLayout;
3006
3007 // We want the point to be mapped to the dragTarget.
3008 if (dropTargetLayout != null) {
3009 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
3010 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3011 } else {
3012 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
3013 }
3014 }
3015
3016 int snapScreen = -1;
3017 boolean resizeOnDrop = false;
3018 if (d.dragSource != this) {
3019 final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
3020 (int) mDragViewVisualCenter[1] };
3021 onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
3022 } else if (mDragInfo != null) {
3023 final View cell = mDragInfo.cell;
3024
3025 Runnable resizeRunnable = null;
3026 if (dropTargetLayout != null && !d.cancelled) {
3027 // Move internally
3028 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
3029 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
3030 long container = hasMovedIntoHotseat ?
3031 LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3032 LauncherSettings.Favorites.CONTAINER_DESKTOP;
3033 long screenId = (mTargetCell[0] < 0) ?
3034 mDragInfo.screenId : getIdForScreen(dropTargetLayout);
3035 int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
3036 int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
3037 // First we find the cell nearest to point at which the item is
3038 // dropped, without any consideration to whether there is an item there.
3039
3040 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
3041 mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
3042 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3043 mDragViewVisualCenter[1], mTargetCell);
3044
3045 // If the item being dropped is a shortcut and the nearest drop
3046 // cell also contains a shortcut, then create a folder with the two shortcuts.
3047 if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
3048 dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
3049 return;
3050 }
3051
3052 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
3053 distance, d, false)) {
3054 return;
3055 }
3056
3057 // Aside from the special case where we're dropping a shortcut onto a shortcut,
3058 // we need to find the nearest cell location that is vacant
3059 ItemInfo item = (ItemInfo) d.dragInfo;
3060 int minSpanX = item.spanX;
3061 int minSpanY = item.spanY;
3062 if (item.minSpanX > 0 && item.minSpanY > 0) {
3063 minSpanX = item.minSpanX;
3064 minSpanY = item.minSpanY;
3065 }
3066
3067 int[] resultSpan = new int[2];
3068 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3069 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
3070 mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
3071
3072 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
3073
3074 // if the widget resizes on drop
3075 if (foundCell && (cell instanceof AppWidgetHostView) &&
3076 (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
3077 resizeOnDrop = true;
3078 item.spanX = resultSpan[0];
3079 item.spanY = resultSpan[1];
3080 AppWidgetHostView awhv = (AppWidgetHostView) cell;
3081 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
3082 resultSpan[1]);
3083 }
3084
3085 if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
3086 snapScreen = getPageIndexForScreenId(screenId);
3087 snapToPage(snapScreen);
3088 }
3089
3090 if (foundCell) {
3091 final ItemInfo info = (ItemInfo) cell.getTag();
3092 if (hasMovedLayouts) {
3093 // Reparent the view
3094 CellLayout parentCell = getParentCellLayoutForView(cell);
3095 if (parentCell != null) {
3096 parentCell.removeView(cell);
3097 } else if (LauncherAppState.isDogfoodBuild()) {
3098 throw new NullPointerException("mDragInfo.cell has null parent");
3099 }
3100 addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
3101 info.spanX, info.spanY);
3102 }
3103
3104 // update the item's position after drop
3105 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
3106 lp.cellX = lp.tmpCellX = mTargetCell[0];
3107 lp.cellY = lp.tmpCellY = mTargetCell[1];
3108 lp.cellHSpan = item.spanX;
3109 lp.cellVSpan = item.spanY;
3110 lp.isLockedToGrid = true;
3111
3112 if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3113 cell instanceof LauncherAppWidgetHostView) {
3114 final CellLayout cellLayout = dropTargetLayout;
3115 // We post this call so that the widget has a chance to be placed
3116 // in its final location
3117
3118 final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
3119 AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
3120 if (pinfo != null &&
3121 pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
3122 final Runnable addResizeFrame = new Runnable() {
3123 public void run() {
3124 DragLayer dragLayer = mLauncher.getDragLayer();
3125 dragLayer.addResizeFrame(info, hostView, cellLayout);
3126 }
3127 };
3128 resizeRunnable = (new Runnable() {
3129 public void run() {
3130 if (!isPageMoving()) {
3131 addResizeFrame.run();
3132 } else {
3133 mDelayedResizeRunnable = addResizeFrame;
3134 }
3135 }
3136 });
3137 }
3138 }
3139
3140 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
3141 lp.cellY, item.spanX, item.spanY);
3142 } else {
3143 // If we can't find a drop location, we return the item to its original position
3144 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
3145 mTargetCell[0] = lp.cellX;
3146 mTargetCell[1] = lp.cellY;
3147 CellLayout layout = (CellLayout) cell.getParent().getParent();
3148 layout.markCellsAsOccupiedForView(cell);
3149 }
3150 }
3151
3152 final CellLayout parent = (CellLayout) cell.getParent().getParent();
3153 final Runnable finalResizeRunnable = resizeRunnable;
3154 // Prepare it to be animated into its new position
3155 // This must be called after the view has been re-parented
3156 final Runnable onCompleteRunnable = new Runnable() {
3157 @Override
3158 public void run() {
3159 mAnimatingViewIntoPlace = false;
3160 updateChildrenLayersEnabled(false);
3161 if (finalResizeRunnable != null) {
3162 finalResizeRunnable.run();
3163 }
3164 }
3165 };
3166 mAnimatingViewIntoPlace = true;
3167 if (d.dragView.hasDrawn()) {
3168 final ItemInfo info = (ItemInfo) cell.getTag();
3169 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
3170 int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
3171 ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3172 animateWidgetDrop(info, parent, d.dragView,
3173 onCompleteRunnable, animationType, cell, false);
3174 } else {
3175 int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
3176 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
3177 onCompleteRunnable, this);
3178 }
3179 } else {
3180 d.deferDragViewCleanupPostAnimation = false;
3181 cell.setVisibility(VISIBLE);
3182 }
3183 parent.onDropChild(cell);
3184 }
3185 }
3186
3187 public void setFinalScrollForPageChange(int pageIndex) {
3188 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3189 if (cl != null) {
3190 mSavedScrollX = getScrollX();
3191 mSavedTranslationX = cl.getTranslationX();
3192 mSavedRotationY = cl.getRotationY();
3193 final int newX = getScrollForPage(pageIndex);
3194 setScrollX(newX);
3195 cl.setTranslationX(0f);
3196 cl.setRotationY(0f);
3197 }
3198 }
3199
3200 public void resetFinalScrollForPageChange(int pageIndex) {
3201 if (pageIndex >= 0) {
3202 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3203 setScrollX(mSavedScrollX);
3204 cl.setTranslationX(mSavedTranslationX);
3205 cl.setRotationY(mSavedRotationY);
3206 }
3207 }
3208
3209 public void getViewLocationRelativeToSelf(View v, int[] location) {
3210 getLocationInWindow(location);
3211 int x = location[0];
3212 int y = location[1];
3213
3214 v.getLocationInWindow(location);
3215 int vX = location[0];
3216 int vY = location[1];
3217
3218 location[0] = vX - x;
3219 location[1] = vY - y;
3220 }
3221
3222 public void onDragEnter(DragObject d) {
3223 mDragEnforcer.onDragEnter();
3224 mCreateUserFolderOnDrop = false;
3225 mAddToExistingFolderOnDrop = false;
3226
3227 mDropToLayout = null;
3228 CellLayout layout = getCurrentDropLayout();
3229 setCurrentDropLayout(layout);
3230 setCurrentDragOverlappingLayout(layout);
3231
3232 if (!workspaceInModalState()) {
3233 mLauncher.getDragLayer().showPageHints();
3234 }
3235 }
3236
3237 /** Return a rect that has the cellWidth/cellHeight (left, top), and
3238 * widthGap/heightGap (right, bottom) */
3239 static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
3240 LauncherAppState app = LauncherAppState.getInstance();
3241 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3242
3243 Display display = launcher.getWindowManager().getDefaultDisplay();
3244 Point smallestSize = new Point();
3245 Point largestSize = new Point();
3246 display.getCurrentSizeRange(smallestSize, largestSize);
3247 int countX = (int) grid.numColumns;
3248 int countY = (int) grid.numRows;
3249 if (orientation == CellLayout.LANDSCAPE) {
3250 if (mLandscapeCellLayoutMetrics == null) {
3251 Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
3252 int width = largestSize.x - padding.left - padding.right;
3253 int height = smallestSize.y - padding.top - padding.bottom;
3254 mLandscapeCellLayoutMetrics = new Rect();
3255 mLandscapeCellLayoutMetrics.set(
3256 grid.calculateCellWidth(width, countX),
3257 grid.calculateCellHeight(height, countY), 0, 0);
3258 }
3259 return mLandscapeCellLayoutMetrics;
3260 } else if (orientation == CellLayout.PORTRAIT) {
3261 if (mPortraitCellLayoutMetrics == null) {
3262 Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
3263 int width = smallestSize.x - padding.left - padding.right;
3264 int height = largestSize.y - padding.top - padding.bottom;
3265 mPortraitCellLayoutMetrics = new Rect();
3266 mPortraitCellLayoutMetrics.set(
3267 grid.calculateCellWidth(width, countX),
3268 grid.calculateCellHeight(height, countY), 0, 0);
3269 }
3270 return mPortraitCellLayoutMetrics;
3271 }
3272 return null;
3273 }
3274
3275 public void onDragExit(DragObject d) {
3276 mDragEnforcer.onDragExit();
3277
3278 // Here we store the final page that will be dropped to, if the workspace in fact
3279 // receives the drop
3280 if (mInScrollArea) {
3281 if (isPageMoving()) {
3282 // If the user drops while the page is scrolling, we should use that page as the
3283 // destination instead of the page that is being hovered over.
3284 mDropToLayout = (CellLayout) getPageAt(getNextPage());
3285 } else {
3286 mDropToLayout = mDragOverlappingLayout;
3287 }
3288 } else {
3289 mDropToLayout = mDragTargetLayout;
3290 }
3291
3292 if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
3293 mCreateUserFolderOnDrop = true;
3294 } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
3295 mAddToExistingFolderOnDrop = true;
3296 }
3297
3298 // Reset the scroll area and previous drag target
3299 onResetScrollArea();
3300 setCurrentDropLayout(null);
3301 setCurrentDragOverlappingLayout(null);
3302
3303 mSpringLoadedDragController.cancel();
3304
3305 if (!mIsPageMoving) {
3306 hideOutlines();
3307 }
3308 mLauncher.getDragLayer().hidePageHints();
3309 }
3310
3311 void setCurrentDropLayout(CellLayout layout) {
3312 if (mDragTargetLayout != null) {
3313 mDragTargetLayout.revertTempState();
3314 mDragTargetLayout.onDragExit();
3315 }
3316 mDragTargetLayout = layout;
3317 if (mDragTargetLayout != null) {
3318 mDragTargetLayout.onDragEnter();
3319 }
3320 cleanupReorder(true);
3321 cleanupFolderCreation();
3322 setCurrentDropOverCell(-1, -1);
3323 }
3324
3325 void setCurrentDragOverlappingLayout(CellLayout layout) {
3326 if (mDragOverlappingLayout != null) {
3327 mDragOverlappingLayout.setIsDragOverlapping(false);
3328 }
3329 mDragOverlappingLayout = layout;
3330 if (mDragOverlappingLayout != null) {
3331 mDragOverlappingLayout.setIsDragOverlapping(true);
3332 }
3333 invalidate();
3334 }
3335
3336 void setCurrentDropOverCell(int x, int y) {
3337 if (x != mDragOverX || y != mDragOverY) {
3338 mDragOverX = x;
3339 mDragOverY = y;
3340 setDragMode(DRAG_MODE_NONE);
3341 }
3342 }
3343
3344 void setDragMode(int dragMode) {
3345 if (dragMode != mDragMode) {
3346 if (dragMode == DRAG_MODE_NONE) {
3347 cleanupAddToFolder();
3348 // We don't want to cancel the re-order alarm every time the target cell changes
3349 // as this feels to slow / unresponsive.
3350 cleanupReorder(false);
3351 cleanupFolderCreation();
3352 } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
3353 cleanupReorder(true);
3354 cleanupFolderCreation();
3355 } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
3356 cleanupAddToFolder();
3357 cleanupReorder(true);
3358 } else if (dragMode == DRAG_MODE_REORDER) {
3359 cleanupAddToFolder();
3360 cleanupFolderCreation();
3361 }
3362 mDragMode = dragMode;
3363 }
3364 }
3365
3366 private void cleanupFolderCreation() {
3367 if (mDragFolderRingAnimator != null) {
3368 mDragFolderRingAnimator.animateToNaturalState();
3369 mDragFolderRingAnimator = null;
3370 }
3371 mFolderCreationAlarm.setOnAlarmListener(null);
3372 mFolderCreationAlarm.cancelAlarm();
3373 }
3374
3375 private void cleanupAddToFolder() {
3376 if (mDragOverFolderIcon != null) {
3377 mDragOverFolderIcon.onDragExit(null);
3378 mDragOverFolderIcon = null;
3379 }
3380 }
3381
3382 private void cleanupReorder(boolean cancelAlarm) {
3383 // Any pending reorders are canceled
3384 if (cancelAlarm) {
3385 mReorderAlarm.cancelAlarm();
3386 }
3387 mLastReorderX = -1;
3388 mLastReorderY = -1;
3389 }
3390
3391 /*
3392 *
3393 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
3394 * coordinate space. The argument xy is modified with the return result.
3395 *
3396 * if cachedInverseMatrix is not null, this method will just use that matrix instead of
3397 * computing it itself; we use this to avoid redundant matrix inversions in
3398 * findMatchingPageForDragOver
3399 *
3400 */
3401 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
3402 xy[0] = xy[0] - v.getLeft();
3403 xy[1] = xy[1] - v.getTop();
3404 }
3405
3406 boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
3407 if (r == null) {
3408 r = new Rect();
3409 }
3410 mTempPt[0] = x;
3411 mTempPt[1] = y;
3412 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3413
3414 LauncherAppState app = LauncherAppState.getInstance();
3415 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3416 r = grid.getHotseatRect();
3417 if (r.contains(mTempPt[0], mTempPt[1])) {
3418 return true;
3419 }
3420 return false;
3421 }
3422
3423 void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
3424 mTempPt[0] = (int) xy[0];
3425 mTempPt[1] = (int) xy[1];
3426 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3427 mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
3428
3429 xy[0] = mTempPt[0];
3430 xy[1] = mTempPt[1];
3431 }
3432
3433 /*
3434 *
3435 * Convert the 2D coordinate xy from this CellLayout's coordinate space to
3436 * the parent View's coordinate space. The argument xy is modified with the return result.
3437 *
3438 */
3439 void mapPointFromChildToSelf(View v, float[] xy) {
3440 xy[0] += v.getLeft();
3441 xy[1] += v.getTop();
3442 }
3443
3444 static private float squaredDistance(float[] point1, float[] point2) {
3445 float distanceX = point1[0] - point2[0];
3446 float distanceY = point2[1] - point2[1];
3447 return distanceX * distanceX + distanceY * distanceY;
3448 }
3449
3450 /*
3451 *
3452 * This method returns the CellLayout that is currently being dragged to. In order to drag
3453 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
3454 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
3455 *
3456 * Return null if no CellLayout is currently being dragged over
3457 *
3458 */
3459 private CellLayout findMatchingPageForDragOver(
3460 DragView dragView, float originX, float originY, boolean exact) {
3461 // We loop through all the screens (ie CellLayouts) and see which ones overlap
3462 // with the item being dragged and then choose the one that's closest to the touch point
3463 final int screenCount = getChildCount();
3464 CellLayout bestMatchingScreen = null;
3465 float smallestDistSoFar = Float.MAX_VALUE;
3466
3467 for (int i = 0; i < screenCount; i++) {
3468 // The custom content screen is not a valid drag over option
3469 if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
3470 continue;
3471 }
3472
3473 CellLayout cl = (CellLayout) getChildAt(i);
3474
3475 final float[] touchXy = {originX, originY};
3476 // Transform the touch coordinates to the CellLayout's local coordinates
3477 // If the touch point is within the bounds of the cell layout, we can return immediately
3478 cl.getMatrix().invert(mTempInverseMatrix);
3479 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
3480
3481 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
3482 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
3483 return cl;
3484 }
3485
3486 if (!exact) {
3487 // Get the center of the cell layout in screen coordinates
3488 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
3489 cellLayoutCenter[0] = cl.getWidth()/2;
3490 cellLayoutCenter[1] = cl.getHeight()/2;
3491 mapPointFromChildToSelf(cl, cellLayoutCenter);
3492
3493 touchXy[0] = originX;
3494 touchXy[1] = originY;
3495
3496 // Calculate the distance between the center of the CellLayout
3497 // and the touch point
3498 float dist = squaredDistance(touchXy, cellLayoutCenter);
3499
3500 if (dist < smallestDistSoFar) {
3501 smallestDistSoFar = dist;
3502 bestMatchingScreen = cl;
3503 }
3504 }
3505 }
3506 return bestMatchingScreen;
3507 }
3508
3509 // This is used to compute the visual center of the dragView. This point is then
3510 // used to visualize drop locations and determine where to drop an item. The idea is that
3511 // the visual center represents the user's interpretation of where the item is, and hence
3512 // is the appropriate point to use when determining drop location.
3513 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
3514 DragView dragView, float[] recycle) {
3515 float res[];
3516 if (recycle == null) {
3517 res = new float[2];
3518 } else {
3519 res = recycle;
3520 }
3521
3522 // First off, the drag view has been shifted in a way that is not represented in the
3523 // x and y values or the x/yOffsets. Here we account for that shift.
3524 x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
3525 y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
3526
3527 // These represent the visual top and left of drag view if a dragRect was provided.
3528 // If a dragRect was not provided, then they correspond to the actual view left and
3529 // top, as the dragRect is in that case taken to be the entire dragView.
3530 // R.dimen.dragViewOffsetY.
3531 int left = x - xOffset;
3532 int top = y - yOffset;
3533
3534 // In order to find the visual center, we shift by half the dragRect
3535 res[0] = left + dragView.getDragRegion().width() / 2;
3536 res[1] = top + dragView.getDragRegion().height() / 2;
3537
3538 return res;
3539 }
3540
3541 private boolean isDragWidget(DragObject d) {
3542 return (d.dragInfo instanceof LauncherAppWidgetInfo ||
3543 d.dragInfo instanceof PendingAddWidgetInfo);
3544 }
3545 private boolean isExternalDragWidget(DragObject d) {
3546 return d.dragSource != this && isDragWidget(d);
3547 }
3548
3549 public void onDragOver(DragObject d) {
3550 // Skip drag over events while we are dragging over side pages
3551 if (mInScrollArea || !transitionStateShouldAllowDrop()) return;
3552
3553 Rect r = new Rect();
3554 CellLayout layout = null;
3555 ItemInfo item = (ItemInfo) d.dragInfo;
3556 if (item == null) {
3557 if (LauncherAppState.isDogfoodBuild()) {
3558 throw new NullPointerException("DragObject has null info");
3559 }
3560 return;
3561 }
3562
3563 // Ensure that we have proper spans for the item that we are dropping
3564 if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
3565 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
3566 d.dragView, mDragViewVisualCenter);
3567
3568 final View child = (mDragInfo == null) ? null : mDragInfo.cell;
3569 // Identify whether we have dragged over a side page
3570 if (workspaceInModalState()) {
3571 if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
3572 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3573 layout = mLauncher.getHotseat().getLayout();
3574 }
3575 }
3576 if (layout == null) {
3577 layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
3578 }
3579 if (layout != mDragTargetLayout) {
3580 setCurrentDropLayout(layout);
3581 setCurrentDragOverlappingLayout(layout);
3582
3583 boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
3584 if (isInSpringLoadedMode) {
3585 if (mLauncher.isHotseatLayout(layout)) {
3586 mSpringLoadedDragController.cancel();
3587 } else {
3588 mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3589 }
3590 }
3591 }
3592 } else {
3593 // Test to see if we are over the hotseat otherwise just use the current page
3594 if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
3595 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3596 layout = mLauncher.getHotseat().getLayout();
3597 }
3598 }
3599 if (layout == null) {
3600 layout = getCurrentDropLayout();
3601 }
3602 if (layout != mDragTargetLayout) {
3603 setCurrentDropLayout(layout);
3604 setCurrentDragOverlappingLayout(layout);
3605 }
3606 }
3607
3608 // Handle the drag over
3609 if (mDragTargetLayout != null) {
3610 // We want the point to be mapped to the dragTarget.
3611 if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3612 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3613 } else {
3614 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
3615 }
3616
3617 ItemInfo info = (ItemInfo) d.dragInfo;
3618
3619 int minSpanX = item.spanX;
3620 int minSpanY = item.spanY;
3621 if (item.minSpanX > 0 && item.minSpanY > 0) {
3622 minSpanX = item.minSpanX;
3623 minSpanY = item.minSpanY;
3624 }
3625
3626 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3627 (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
3628 mDragTargetLayout, mTargetCell);
3629 int reorderX = mTargetCell[0];
3630 int reorderY = mTargetCell[1];
3631
3632 setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3633
3634 float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
3635 mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
3636
3637 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
3638 mTargetCell[1]);
3639
3640 manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
3641 targetCellDistance, dragOverView);
3642
3643 boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
3644 mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
3645 item.spanY, child, mTargetCell);
3646
3647 if (!nearestDropOccupied) {
3648 mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3649 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3650 mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
3651 d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
3652 } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
3653 && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
3654 mLastReorderY != reorderY)) {
3655
3656 int[] resultSpan = new int[2];
3657 mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3658 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
3659 child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
3660
3661 // Otherwise, if we aren't adding to or creating a folder and there's no pending
3662 // reorder, then we schedule a reorder
3663 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
3664 minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
3665 mReorderAlarm.setOnAlarmListener(listener);
3666 mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3667 }
3668
3669 if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
3670 !nearestDropOccupied) {
3671 if (mDragTargetLayout != null) {
3672 mDragTargetLayout.revertTempState();
3673 }
3674 }
3675 }
3676 }
3677
3678 private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
3679 int[] targetCell, float distance, View dragOverView) {
3680 boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
3681 false);
3682
3683 if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3684 !mFolderCreationAlarm.alarmPending()) {
3685 mFolderCreationAlarm.setOnAlarmListener(new
3686 FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
3687 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3688 return;
3689 }
3690
3691 boolean willAddToFolder =
3692 willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
3693
3694 if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3695 mDragOverFolderIcon = ((FolderIcon) dragOverView);
3696 mDragOverFolderIcon.onDragEnter(info);
3697 if (targetLayout != null) {
3698 targetLayout.clearDragOutlines();
3699 }
3700 setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3701 return;
3702 }
3703
3704 if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3705 setDragMode(DRAG_MODE_NONE);
3706 }
3707 if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3708 setDragMode(DRAG_MODE_NONE);
3709 }
3710
3711 return;
3712 }
3713
3714 class FolderCreationAlarmListener implements OnAlarmListener {
3715 CellLayout layout;
3716 int cellX;
3717 int cellY;
3718
3719 public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3720 this.layout = layout;
3721 this.cellX = cellX;
3722 this.cellY = cellY;
3723 }
3724
3725 public void onAlarm(Alarm alarm) {
3726 if (mDragFolderRingAnimator != null) {
3727 // This shouldn't happen ever, but just in case, make sure we clean up the mess.
3728 mDragFolderRingAnimator.animateToNaturalState();
3729 }
3730 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
3731 mDragFolderRingAnimator.setCell(cellX, cellY);
3732 mDragFolderRingAnimator.setCellLayout(layout);
3733 mDragFolderRingAnimator.animateToAcceptState();
3734 layout.showFolderAccept(mDragFolderRingAnimator);
3735 layout.clearDragOutlines();
3736 setDragMode(DRAG_MODE_CREATE_FOLDER);
3737 }
3738 }
3739
3740 class ReorderAlarmListener implements OnAlarmListener {
3741 float[] dragViewCenter;
3742 int minSpanX, minSpanY, spanX, spanY;
3743 DragView dragView;
3744 View child;
3745
3746 public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
3747 int spanY, DragView dragView, View child) {
3748 this.dragViewCenter = dragViewCenter;
3749 this.minSpanX = minSpanX;
3750 this.minSpanY = minSpanY;
3751 this.spanX = spanX;
3752 this.spanY = spanY;
3753 this.child = child;
3754 this.dragView = dragView;
3755 }
3756
3757 public void onAlarm(Alarm alarm) {
3758 int[] resultSpan = new int[2];
3759 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3760 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
3761 mTargetCell);
3762 mLastReorderX = mTargetCell[0];
3763 mLastReorderY = mTargetCell[1];
3764
3765 mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3766 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
3767 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
3768
3769 if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
3770 mDragTargetLayout.revertTempState();
3771 } else {
3772 setDragMode(DRAG_MODE_REORDER);
3773 }
3774
3775 boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
3776 mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3777 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3778 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
3779 dragView.getDragVisualizeOffset(), dragView.getDragRegion());
3780 }
3781 }
3782
3783 @Override
3784 public void getHitRectRelativeToDragLayer(Rect outRect) {
3785 // We want the workspace to have the whole area of the display (it will find the correct
3786 // cell layout to drop to in the existing drag/drop logic.
3787 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3788 }
3789
3790 /**
3791 * Add the item specified by dragInfo to the given layout.
3792 * @return true if successful
3793 */
3794 public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
3795 if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
3796 onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
3797 return true;
3798 }
3799 mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
3800 return false;
3801 }
3802
3803 private void onDropExternal(int[] touchXY, Object dragInfo,
3804 CellLayout cellLayout, boolean insertAtFirst) {
3805 onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
3806 }
3807
3808 /**
3809 * Drop an item that didn't originate on one of the workspace screens.
3810 * It may have come from Launcher (e.g. from all apps or customize), or it may have
3811 * come from another app altogether.
3812 *
3813 * NOTE: This can also be called when we are outside of a drag event, when we want
3814 * to add an item to one of the workspace screens.
3815 */
3816 private void onDropExternal(final int[] touchXY, final Object dragInfo,
3817 final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3818 final Runnable exitSpringLoadedRunnable = new Runnable() {
3819 @Override
3820 public void run() {
3821 mLauncher.exitSpringLoadedDragModeDelayed(true,
3822 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
3823 }
3824 };
3825
3826 ItemInfo info = (ItemInfo) dragInfo;
3827 int spanX = info.spanX;
3828 int spanY = info.spanY;
3829 if (mDragInfo != null) {
3830 spanX = mDragInfo.spanX;
3831 spanY = mDragInfo.spanY;
3832 }
3833
3834 final long container = mLauncher.isHotseatLayout(cellLayout) ?
3835 LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3836 LauncherSettings.Favorites.CONTAINER_DESKTOP;
3837 final long screenId = getIdForScreen(cellLayout);
3838 if (!mLauncher.isHotseatLayout(cellLayout)
3839 && screenId != getScreenIdForPageIndex(mCurrentPage)
3840 && mState != State.SPRING_LOADED) {
3841 snapToScreenId(screenId, null);
3842 }
3843
3844 if (info instanceof PendingAddItemInfo) {
3845 final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3846
3847 boolean findNearestVacantCell = true;
3848 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3849 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3850 cellLayout, mTargetCell);
3851 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3852 mDragViewVisualCenter[1], mTargetCell);
3853 if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
3854 distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
3855 cellLayout, mTargetCell, distance)) {
3856 findNearestVacantCell = false;
3857 }
3858 }
3859
3860 final ItemInfo item = (ItemInfo) d.dragInfo;
3861 boolean updateWidgetSize = false;
3862 if (findNearestVacantCell) {
3863 int minSpanX = item.spanX;
3864 int minSpanY = item.spanY;
3865 if (item.minSpanX > 0 && item.minSpanY > 0) {
3866 minSpanX = item.minSpanX;
3867 minSpanY = item.minSpanY;
3868 }
3869 int[] resultSpan = new int[2];
3870 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
3871 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3872 null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3873
3874 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3875 updateWidgetSize = true;
3876 }
3877 item.spanX = resultSpan[0];
3878 item.spanY = resultSpan[1];
3879 }
3880
3881 Runnable onAnimationCompleteRunnable = new Runnable() {
3882 @Override
3883 public void run() {
3884 // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
3885 // adding an item that may not be dropped right away (due to a config activity)
3886 // we defer the removal until the activity returns.
3887 deferRemoveExtraEmptyScreen();
3888
3889 // When dragging and dropping from customization tray, we deal with creating
3890 // widgets/shortcuts/folders in a slightly different way
3891 switch (pendingInfo.itemType) {
3892 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3893 int span[] = new int[2];
3894 span[0] = item.spanX;
3895 span[1] = item.spanY;
3896 mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3897 container, screenId, mTargetCell, span, null);
3898 break;
3899 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3900 mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3901 container, screenId, mTargetCell, null);
3902 break;
3903 default:
3904 throw new IllegalStateException("Unknown item type: " +
3905 pendingInfo.itemType);
3906 }
3907 }
3908 };
3909 View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3910 ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3911
3912 if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
3913 AppWidgetHostView awhv = (AppWidgetHostView) finalView;
3914 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
3915 item.spanY);
3916 }
3917
3918 int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3919 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3920 ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3921 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3922 }
3923 animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3924 animationStyle, finalView, true);
3925 } else {
3926 // This is for other drag/drop cases, like dragging from All Apps
3927 View view = null;
3928
3929 switch (info.itemType) {
3930 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3931 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3932 if (info.container == NO_ID && info instanceof AppInfo) {
3933 // Came from all apps -- make a copy
3934 info = new ShortcutInfo((AppInfo) info);
3935 }
3936 view = mLauncher.createShortcut(R.layout.application, cellLayout,
3937 (ShortcutInfo) info);
3938 break;
3939 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3940 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3941 (FolderInfo) info, mIconCache);
3942 break;
3943 default:
3944 throw new IllegalStateException("Unknown item type: " + info.itemType);
3945 }
3946
3947 // First we find the cell nearest to point at which the item is
3948 // dropped, without any consideration to whether there is an item there.
3949 if (touchXY != null) {
3950 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3951 cellLayout, mTargetCell);
3952 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3953 mDragViewVisualCenter[1], mTargetCell);
3954 d.postAnimationRunnable = exitSpringLoadedRunnable;
3955 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3956 true, d.dragView, d.postAnimationRunnable)) {
3957 return;
3958 }
3959 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3960 true)) {
3961 return;
3962 }
3963 }
3964
3965 if (touchXY != null) {
3966 // when dragging and dropping, just find the closest free spot
3967 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
3968 (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3969 null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3970 } else {
3971 cellLayout.findCellForSpan(mTargetCell, 1, 1);
3972 }
3973 // Add the item to DB before adding to screen ensures that the container and other
3974 // values of the info is properly updated.
3975 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
3976 mTargetCell[0], mTargetCell[1]);
3977
3978 addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
3979 info.spanY, insertAtFirst);
3980 cellLayout.onDropChild(view);
3981 cellLayout.getShortcutsAndWidgets().measureChild(view);
3982
3983 if (d.dragView != null) {
3984 // We wrap the animation call in the temporary set and reset of the current
3985 // cellLayout to its final transform -- this means we animate the drag view to
3986 // the correct final location.
3987 setFinalTransitionTransform(cellLayout);
3988 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3989 exitSpringLoadedRunnable, this);
3990 resetTransitionTransform(cellLayout);
3991 }
3992 }
3993 }
3994
3995 public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3996 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
3997 widgetInfo.spanY, widgetInfo, false);
3998 int visibility = layout.getVisibility();
3999 layout.setVisibility(VISIBLE);
4000
4001 int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
4002 int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
4003 Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
4004 Bitmap.Config.ARGB_8888);
4005 mCanvas.setBitmap(b);
4006
4007 layout.measure(width, height);
4008 layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
4009 layout.draw(mCanvas);
4010 mCanvas.setBitmap(null);
4011 layout.setVisibility(visibility);
4012 return b;
4013 }
4014
4015 private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
4016 DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
4017 boolean external, boolean scale) {
4018 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
4019 // location and size on the home screen.
4020 int spanX = info.spanX;
4021 int spanY = info.spanY;
4022
4023 Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
4024 loc[0] = r.left;
4025 loc[1] = r.top;
4026
4027 setFinalTransitionTransform(layout);
4028 float cellLayoutScale =
4029 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
4030 resetTransitionTransform(layout);
4031
4032 float dragViewScaleX;
4033 float dragViewScaleY;
4034 if (scale) {
4035 dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
4036 dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
4037 } else {
4038 dragViewScaleX = 1f;
4039 dragViewScaleY = 1f;
4040 }
4041
4042 // The animation will scale the dragView about its center, so we need to center about
4043 // the final location.
4044 loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
4045 loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
4046
4047 scaleXY[0] = dragViewScaleX * cellLayoutScale;
4048 scaleXY[1] = dragViewScaleY * cellLayoutScale;
4049 }
4050
4051 public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
4052 final Runnable onCompleteRunnable, int animationType, final View finalView,
4053 boolean external) {
4054 Rect from = new Rect();
4055 mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
4056
4057 int[] finalPos = new int[2];
4058 float scaleXY[] = new float[2];
4059 boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
4060 getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
4061 external, scalePreview);
4062
4063 Resources res = mLauncher.getResources();
4064 final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
4065
4066 // In the case where we've prebound the widget, we remove it from the DragLayer
4067 if (finalView instanceof AppWidgetHostView && external) {
4068 Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
4069 mLauncher.getDragLayer().removeView(finalView);
4070 }
4071 if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
4072 Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
4073 dragView.setCrossFadeBitmap(crossFadeBitmap);
4074 dragView.crossFade((int) (duration * 0.8f));
4075 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
4076 scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
4077 }
4078
4079 DragLayer dragLayer = mLauncher.getDragLayer();
4080 if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
4081 mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
4082 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
4083 } else {
4084 int endStyle;
4085 if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
4086 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
4087 } else {
4088 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
4089 }
4090
4091 Runnable onComplete = new Runnable() {
4092 @Override
4093 public void run() {
4094 if (finalView != null) {
4095 finalView.setVisibility(VISIBLE);
4096 }
4097 if (onCompleteRunnable != null) {
4098 onCompleteRunnable.run();
4099 }
4100 }
4101 };
4102 dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
4103 finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
4104 duration, this);
4105 }
4106 }
4107
4108 public void setFinalTransitionTransform(CellLayout layout) {
4109 if (isSwitchingState()) {
4110 mCurrentScale = getScaleX();
4111 setScaleX(mNewScale);
4112 setScaleY(mNewScale);
4113 }
4114 }
4115 public void resetTransitionTransform(CellLayout layout) {
4116 if (isSwitchingState()) {
4117 setScaleX(mCurrentScale);
4118 setScaleY(mCurrentScale);
4119 }
4120 }
4121
4122 /**
4123 * Return the current {@link CellLayout}, correctly picking the destination
4124 * screen while a scroll is in progress.
4125 */
4126 public CellLayout getCurrentDropLayout() {
4127 return (CellLayout) getChildAt(getNextPage());
4128 }
4129
4130 /**
4131 * Return the current CellInfo describing our current drag; this method exists
4132 * so that Launcher can sync this object with the correct info when the activity is created/
4133 * destroyed
4134 *
4135 */
4136 public CellLayout.CellInfo getDragInfo() {
4137 return mDragInfo;
4138 }
4139
4140 public int getCurrentPageOffsetFromCustomContent() {
4141 return getNextPage() - numCustomPages();
4142 }
4143
4144 /**
4145 * Calculate the nearest cell where the given object would be dropped.
4146 *
4147 * pixelX and pixelY should be in the coordinate system of layout
4148 */
4149 private int[] findNearestArea(int pixelX, int pixelY,
4150 int spanX, int spanY, CellLayout layout, int[] recycle) {
4151 return layout.findNearestArea(
4152 pixelX, pixelY, spanX, spanY, recycle);
4153 }
4154
4155 void setup(DragController dragController) {
4156 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
4157 mDragController = dragController;
4158
4159 // hardware layers on children are enabled on startup, but should be disabled until
4160 // needed
4161 updateChildrenLayersEnabled(false);
4162 }
4163
4164 /**
4165 * Called at the end of a drag which originated on the workspace.
4166 */
4167 public void onDropCompleted(final View target, final DragObject d,
4168 final boolean isFlingToDelete, final boolean success) {
4169 if (mDeferDropAfterUninstall) {
4170 mDeferredAction = new Runnable() {
4171 public void run() {
4172 onDropCompleted(target, d, isFlingToDelete, success);
4173 mDeferredAction = null;
4174 }
4175 };
4176 return;
4177 }
4178
4179 boolean beingCalledAfterUninstall = mDeferredAction != null;
4180
4181 if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
4182 if (target != this && mDragInfo != null) {
4183 CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
4184 if (parentCell != null) {
4185 parentCell.removeView(mDragInfo.cell);
4186 } else if (LauncherAppState.isDogfoodBuild()) {
4187 throw new NullPointerException("mDragInfo.cell has null parent");
4188 }
4189 if (mDragInfo.cell instanceof DropTarget) {
4190 mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
4191 }
4192 }
4193 } else if (mDragInfo != null) {
4194 CellLayout cellLayout;
4195 if (mLauncher.isHotseatLayout(target)) {
4196 cellLayout = mLauncher.getHotseat().getLayout();
4197 } else {
4198 cellLayout = getScreenWithId(mDragInfo.screenId);
4199 }
4200 if (cellLayout == null && LauncherAppState.isDogfoodBuild()) {
4201 throw new RuntimeException("Invalid state: cellLayout == null in "
4202 + "Workspace#onDropCompleted. Please file a bug. ");
4203 }
4204 if (cellLayout != null) {
4205 cellLayout.onDropChild(mDragInfo.cell);
4206 }
4207 }
4208 if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
4209 && mDragInfo.cell != null) {
4210 mDragInfo.cell.setVisibility(VISIBLE);
4211 }
4212 mDragOutline = null;
4213 mDragInfo = null;
4214 }
4215
4216 public void deferCompleteDropAfterUninstallActivity() {
4217 mDeferDropAfterUninstall = true;
4218 }
4219
4220 /// maybe move this into a smaller part
4221 public void onUninstallActivityReturned(boolean success) {
4222 mDeferDropAfterUninstall = false;
4223 mUninstallSuccessful = success;
4224 if (mDeferredAction != null) {
4225 mDeferredAction.run();
4226 }
4227 }
4228
4229 void updateItemLocationsInDatabase(CellLayout cl) {
4230 int count = cl.getShortcutsAndWidgets().getChildCount();
4231
4232 long screenId = getIdForScreen(cl);
4233 int container = Favorites.CONTAINER_DESKTOP;
4234
4235 if (mLauncher.isHotseatLayout(cl)) {
4236 screenId = -1;
4237 container = Favorites.CONTAINER_HOTSEAT;
4238 }
4239
4240 for (int i = 0; i < count; i++) {
4241 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4242 ItemInfo info = (ItemInfo) v.getTag();
4243 // Null check required as the AllApps button doesn't have an item info
4244 if (info != null && info.requiresDbUpdate) {
4245 info.requiresDbUpdate = false;
4246 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
4247 info.cellY, info.spanX, info.spanY);
4248 }
4249 }
4250 }
4251
4252 ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplic🔵
4253 ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
4254 getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, fals🔵
4255 int count = getChildCount();
4256 for (int i = 0; i < count; i++) {
4257 CellLayout cl = (CellLayout) getChildAt(i);
4258 getUniqueIntents(cl, uniqueIntents, duplicates, false);
4259 }
4260 return uniqueIntents;
4261 }
4262
4263 void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
4264 ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
4265 int count = cl.getShortcutsAndWidgets().getChildCount();
4266
4267 ArrayList<View> children = new ArrayList<View>();
4268 for (int i = 0; i < count; i++) {
4269 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4270 children.add(v);
4271 }
4272
4273 for (int i = 0; i < count; i++) {
4274 View v = children.get(i);
4275 ItemInfo info = (ItemInfo) v.getTag();
4276 // Null check required as the AllApps button doesn't have an item info
4277 if (info instanceof ShortcutInfo) {
4278 ShortcutInfo si = (ShortcutInfo) info;
4279 ComponentName cn = si.intent.getComponent();
4280
4281 Uri dataUri = si.intent.getData();
4282 // If dataUri is not null / empty or if this component isn't one that would
4283 // have previously showed up in the AllApps list, then this is a widget-type
4284 // shortcut, so ignore it.
4285 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4286 continue;
4287 }
4288
4289 if (!uniqueIntents.contains(cn)) {
4290 uniqueIntents.add(cn);
4291 } else {
4292 if (stripDuplicates) {
4293 cl.removeViewInLayout(v);
4294 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4295 }
4296 if (duplicates != null) {
4297 duplicates.add(cn);
4298 }
4299 }
4300 }
4301 if (v instanceof FolderIcon) {
4302 FolderIcon fi = (FolderIcon) v;
4303 ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
4304 for (int j = 0; j < items.size(); j++) {
4305 if (items.get(j).getTag() instanceof ShortcutInfo) {
4306 ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
4307 ComponentName cn = si.intent.getComponent();
4308
4309 Uri dataUri = si.intent.getData();
4310 // If dataUri is not null / empty or if this component isn't one that would
4311 // have previously showed up in the AllApps list, then this is a widget-type
4312 // shortcut, so ignore it.
4313 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4314 continue;
4315 }
4316
4317 if (!uniqueIntents.contains(cn)) {
4318 uniqueIntents.add(cn);
4319 } else {
4320 if (stripDuplicates) {
4321 fi.getFolderInfo().remove(si);
4322 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4323 }
4324 if (duplicates != null) {
4325 duplicates.add(cn);
4326 }
4327 }
4328 }
4329 }
4330 }
4331 }
4332 }
4333
4334 void saveWorkspaceToDb() {
4335 saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
4336 int count = getChildCount();
4337 for (int i = 0; i < count; i++) {
4338 CellLayout cl = (CellLayout) getChildAt(i);
4339 saveWorkspaceScreenToDb(cl);
4340 }
4341 }
4342
4343 void saveWorkspaceScreenToDb(CellLayout cl) {
4344 int count = cl.getShortcutsAndWidgets().getChildCount();
4345
4346 long screenId = getIdForScreen(cl);
4347 int container = Favorites.CONTAINER_DESKTOP;
4348
4349 Hotseat hotseat = mLauncher.getHotseat();
4350 if (mLauncher.isHotseatLayout(cl)) {
4351 screenId = -1;
4352 container = Favorites.CONTAINER_HOTSEAT;
4353 }
4354
4355 for (int i = 0; i < count; i++) {
4356 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4357 ItemInfo info = (ItemInfo) v.getTag();
4358 // Null check required as the AllApps button doesn't have an item info
4359 if (info != null) {
4360 int cellX = info.cellX;
4361 int cellY = info.cellY;
4362 if (container == Favorites.CONTAINER_HOTSEAT) {
4363 cellX = hotseat.getCellXFromOrder((int) info.screenId);
4364 cellY = hotseat.getCellYFromOrder((int) info.screenId);
4365 }
4366 LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
4367 cellY, false);
4368 }
4369 if (v instanceof FolderIcon) {
4370 FolderIcon fi = (FolderIcon) v;
4371 fi.getFolder().addItemLocationsInDatabase();
4372 }
4373 }
4374 }
4375
4376 @Override
4377 public float getIntrinsicIconScaleFactor() {
4378 return 1f;
4379 }
4380
4381 @Override
4382 public boolean supportsFlingToDelete() {
4383 return true;
4384 }
4385
4386 @Override
4387 public boolean supportsAppInfoDropTarget() {
4388 return false;
4389 }
4390
4391 @Override
4392 public boolean supportsDeleteDropTarget() {
4393 return true;
4394 }
4395
4396 @Override
4397 public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
4398 // Do nothing
4399 }
4400
4401 @Override
4402 public void onFlingToDeleteCompleted() {
4403 // Do nothing
4404 }
4405
4406 public boolean isDropEnabled() {
4407 return true;
4408 }
4409
4410 @Override
4411 protected void onRestoreInstanceState(Parcelable state) {
4412 super.onRestoreInstanceState(state);
4413 Launcher.setScreen(mCurrentPage);
4414 }
4415
4416 @Override
4417 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
4418 // We don't dispatch restoreInstanceState to our children using this code path.
4419 // Some pages will be restored immediately as their items are bound immediately, and
4420 // others we will need to wait until after their items are bound.
4421 mSavedStates = container;
4422 }
4423
4424 public void restoreInstanceStateForChild(int child) {
4425 if (mSavedStates != null) {
4426 mRestoredPages.add(child);
4427 CellLayout cl = (CellLayout) getChildAt(child);
4428 if (cl != null) {
4429 cl.restoreInstanceState(mSavedStates);
4430 }
4431 }
4432 }
4433
4434 public void restoreInstanceStateForRemainingPages() {
4435 int count = getChildCount();
4436 for (int i = 0; i < count; i++) {
4437 if (!mRestoredPages.contains(i)) {
4438 restoreInstanceStateForChild(i);
4439 }
4440 }
4441 mRestoredPages.clear();
4442 mSavedStates = null;
4443 }
4444
4445 @Override
4446 public void scrollLeft() {
4447 if (!workspaceInModalState() && !mIsSwitchingState) {
4448 super.scrollLeft();
4449 }
4450 Folder openFolder = getOpenFolder();
4451 if (openFolder != null) {
4452 openFolder.completeDragExit();
4453 }
4454 }
4455
4456 @Override
4457 public void scrollRight() {
4458 if (!workspaceInModalState() && !mIsSwitchingState) {
4459 super.scrollRight();
4460 }
4461 Folder openFolder = getOpenFolder();
4462 if (openFolder != null) {
4463 openFolder.completeDragExit();
4464 }
4465 }
4466
4467 @Override
4468 public boolean onEnterScrollArea(int x, int y, int direction) {
4469 // Ignore the scroll area if we are dragging over the hot seat
4470 boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
4471 if (mLauncher.getHotseat() != null && isPortrait) {
4472 Rect r = new Rect();
4473 mLauncher.getHotseat().getHitRect(r);
4474 if (r.contains(x, y)) {
4475 return false;
4476 }
4477 }
4478
4479 boolean result = false;
4480 if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) {
4481 mInScrollArea = true;
4482
4483 final int page = getNextPage() +
4484 (direction == DragController.SCROLL_LEFT ? -1 : 1);
4485 // We always want to exit the current layout to ensure parity of enter / exit
4486 setCurrentDropLayout(null);
4487
4488 if (0 <= page && page < getChildCount()) {
4489 // Ensure that we are not dragging over to the custom content screen
4490 if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
4491 return false;
4492 }
4493
4494 CellLayout layout = (CellLayout) getChildAt(page);
4495 setCurrentDragOverlappingLayout(layout);
4496
4497 // Workspace is responsible for drawing the edge glow on adjacent pages,
4498 // so we need to redraw the workspace when this may have changed.
4499 invalidate();
4500 result = true;
4501 }
4502 }
4503 return result;
4504 }
4505
4506 @Override
4507 public boolean onExitScrollArea() {
4508 boolean result = false;
4509 if (mInScrollArea) {
4510 invalidate();
4511 CellLayout layout = getCurrentDropLayout();
4512 setCurrentDropLayout(layout);
4513 setCurrentDragOverlappingLayout(layout);
4514
4515 result = true;
4516 mInScrollArea = false;
4517 }
4518 return result;
4519 }
4520
4521 private void onResetScrollArea() {
4522 setCurrentDragOverlappingLayout(null);
4523 mInScrollArea = false;
4524 }
4525
4526 /**
4527 * Returns a specific CellLayout
4528 */
4529 CellLayout getParentCellLayoutForView(View v) {
4530 ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
4531 for (CellLayout layout : layouts) {
4532 if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
4533 return layout;
4534 }
4535 }
4536 return null;
4537 }
4538
4539 /**
4540 * Returns a list of all the CellLayouts in the workspace.
4541 */
4542 ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
4543 ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
4544 int screenCount = getChildCount();
4545 for (int screen = 0; screen < screenCount; screen++) {
4546 layouts.add(((CellLayout) getChildAt(screen)));
4547 }
4548 if (mLauncher.getHotseat() != null) {
4549 layouts.add(mLauncher.getHotseat().getLayout());
4550 }
4551 return layouts;
4552 }
4553
4554 /**
4555 * We should only use this to search for specific children. Do not use this method to modify
4556 * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
4557 * the hotseat and workspace pages
4558 */
4559 ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
4560 ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4561 new ArrayList<ShortcutAndWidgetContainer>();
4562 int screenCount = getChildCount();
4563 for (int screen = 0; screen < screenCount; screen++) {
4564 childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
4565 }
4566 if (mLauncher.getHotseat() != null) {
4567 childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
4568 }
4569 return childrenLayouts;
4570 }
4571
4572 public Folder getFolderForTag(final Object tag) {
4573 return (Folder) getFirstMatch(new ItemOperator() {
4574
4575 @Override
4576 public boolean evaluate(ItemInfo info, View v, View parent) {
4577 return (v instanceof Folder) && (((Folder) v).getInfo() == tag)
4578 && ((Folder) v).getInfo().opened;
4579 }
4580 });
4581 }
4582
4583 public View getViewForTag(final Object tag) {
4584 return getFirstMatch(new ItemOperator() {
4585
4586 @Override
4587 public boolean evaluate(ItemInfo info, View v, View parent) {
4588 return info == tag;
4589 }
4590 });
4591 }
4592
4593 public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
4594 return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
4595
4596 @Override
4597 public boolean evaluate(ItemInfo info, View v, View parent) {
4598 return (info instanceof LauncherAppWidgetInfo) &&
4599 ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
4600 }
4601 });
4602 }
4603
4604 private View getFirstMatch(final ItemOperator operator) {
4605 final View[] value = new View[1];
4606 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4607 @Override
4608 public boolean evaluate(ItemInfo info, View v, View parent) {
4609 if (operator.evaluate(info, v, parent)) {
4610 value[0] = v;
4611 return true;
4612 }
4613 return false;
4614 }
4615 });
4616 return value[0];
4617 }
4618
4619 void clearDropTargets() {
4620 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4621 @Override
4622 public boolean evaluate(ItemInfo info, View v, View parent) {
4623 if (v instanceof DropTarget) {
4624 mDragController.removeDropTarget((DropTarget) v);
4625 }
4626 // not done, process all the shortcuts
4627 return false;
4628 }
4629 });
4630 }
4631
4632 // Removes ALL items that match a given package name, this is usually called when a package
4633 // has been removed and we want to remove all components (widgets, shortcuts, apps) that
4634 // belong to that package.
4635 void removeItemsByPackageName(final ArrayList<String> packages, final UserHandleCompat user) {
4636 final HashSet<String> packageNames = new HashSet<String>();
4637 packageNames.addAll(packages);
4638
4639 // Filter out all the ItemInfos that this is going to affect
4640 final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
4641 final HashSet<ComponentName> cns = new HashSet<ComponentName>();
4642 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4643 for (CellLayout layoutParent : cellLayouts) {
4644 ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4645 int childCount = layout.getChildCount();
4646 for (int i = 0; i < childCount; ++i) {
4647 View view = layout.getChildAt(i);
4648 infos.add((ItemInfo) view.getTag());
4649 }
4650 }
4651 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4652 @Override
4653 public boolean filterItem(ItemInfo parent, ItemInfo info,
4654 ComponentName cn) {
4655 if (packageNames.contains(cn.getPackageName())
4656 && info.user.equals(user)) {
4657 cns.add(cn);
4658 return true;
4659 }
4660 return false;
4661 }
4662 };
4663 LauncherModel.filterItemInfos(infos, filter);
4664
4665 // Remove the affected components
4666 removeItemsByComponentName(cns, user);
4667 }
4668
4669 // Removes items that match the application info specified, when applications are removed
4670 // as a part of an update, this is called to ensure that other widgets and application
4671 // shortcuts are not removed.
4672 void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos, UserHandleCompat user) {
4673 // Just create a hash table of all the specific components that this will affect
4674 HashSet<ComponentName> cns = new HashSet<ComponentName>();
4675 for (AppInfo info : appInfos) {
4676 cns.add(info.componentName);
4677 }
4678
4679 // Remove all the things
4680 removeItemsByComponentName(cns, user);
4681 }
4682
4683 void removeItemsByComponentName(final HashSet<ComponentName> componentNames,
4684 final UserHandleCompat user) {
4685 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4686 for (final CellLayout layoutParent: cellLayouts) {
4687 final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4688
4689 final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4690 for (int j = 0; j < layout.getChildCount(); j++) {
4691 final View view = layout.getChildAt(j);
4692 children.put((ItemInfo) view.getTag(), view);
4693 }
4694
4695 final ArrayList<View> childrenToRemove = new ArrayList<View>();
4696 final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
4697 new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
4698 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4699 @Override
4700 public boolean filterItem(ItemInfo parent, ItemInfo info,
4701 ComponentName cn) {
4702 if (parent instanceof FolderInfo) {
4703 if (componentNames.contains(cn) && info.user.equals(user)) {
4704 FolderInfo folder = (FolderInfo) parent;
4705 ArrayList<ShortcutInfo> appsToRemove;
4706 if (folderAppsToRemove.containsKey(folder)) {
4707 appsToRemove = folderAppsToRemove.get(folder);
4708 } else {
4709 appsToRemove = new ArrayList<ShortcutInfo>();
4710 folderAppsToRemove.put(folder, appsToRemove);
4711 }
4712 appsToRemove.add((ShortcutInfo) info);
4713 return true;
4714 }
4715 } else {
4716 if (componentNames.contains(cn) && info.user.equals(user)) {
4717 childrenToRemove.add(children.get(info));
4718 return true;
4719 }
4720 }
4721 return false;
4722 }
4723 };
4724 LauncherModel.filterItemInfos(children.keySet(), filter);
4725
4726 // Remove all the apps from their folders
4727 for (FolderInfo folder : folderAppsToRemove.keySet()) {
4728 ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4729 for (ShortcutInfo info : appsToRemove) {
4730 folder.remove(info);
4731 }
4732 }
4733
4734 // Remove all the other children
4735 for (View child : childrenToRemove) {
4736 // Note: We can not remove the view directly from CellLayoutChildren as this
4737 // does not re-mark the spaces as unoccupied.
4738 layoutParent.removeViewInLayout(child);
4739 if (child instanceof DropTarget) {
4740 mDragController.removeDropTarget((DropTarget) child);
4741 }
4742 }
4743
4744 if (childrenToRemove.size() > 0) {
4745 layout.requestLayout();
4746 layout.invalidate();
4747 }
4748 }
4749
4750 // Strip all the empty screens
4751 stripEmptyScreens();
4752 }
4753
4754 interface ItemOperator {
4755 /**
4756 * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}.
4757 *
4758 * @param info info for the shortcut
4759 * @param view view for the shortcut
4760 * @param parent containing folder, or null
4761 * @return true if done, false to continue the map
4762 */
4763 public boolean evaluate(ItemInfo info, View view, View parent);
4764 }
4765
4766 /**
4767 * Map the operator over the shortcuts and widgets, return the first-non-null value.
4768 *
4769 * @param recurse true: iterate over folder children. false: op get the folders themselves.
4770 * @param op the operator to map over the shortcuts
4771 */
4772 void mapOverItems(boolean recurse, ItemOperator op) {
4773 ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
4774 final int containerCount = containers.size();
4775 for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
4776 ShortcutAndWidgetContainer container = containers.get(containerIdx);
4777 // map over all the shortcuts on the workspace
4778 final int itemCount = container.getChildCount();
4779 for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
4780 View item = container.getChildAt(itemIdx);
4781 ItemInfo info = (ItemInfo) item.getTag();
4782 if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
4783 FolderIcon folder = (FolderIcon) item;
4784 ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
4785 // map over all the children in the folder
4786 final int childCount = folderChildren.size();
4787 for (int childIdx = 0; childIdx < childCount; childIdx++) {
4788 View child = folderChildren.get(childIdx);
4789 info = (ItemInfo) child.getTag();
4790 if (op.evaluate(info, child, folder)) {
4791 return;
4792 }
4793 }
4794 } else {
4795 if (op.evaluate(info, item, null)) {
4796 return;
4797 }
4798 }
4799 }
4800 }
4801 }
4802
4803 void updateShortcutsAndWidgets(ArrayList<AppInfo> apps) {
4804 // Break the appinfo list per user
4805 final HashMap<UserHandleCompat, ArrayList<AppInfo>> appsPerUser =
4806 new HashMap<UserHandleCompat, ArrayList<AppInfo>>();
4807 for (AppInfo info : apps) {
4808 ArrayList<AppInfo> filtered = appsPerUser.get(info.user);
4809 if (filtered == null) {
4810 filtered = new ArrayList<AppInfo>();
4811 appsPerUser.put(info.user, filtered);
4812 }
4813 filtered.add(info);
4814 }
4815
4816 for (Map.Entry<UserHandleCompat, ArrayList<AppInfo>> entry : appsPerUser.entrySet()) {
4817 updateShortcutsAndWidgetsPerUser(entry.getValue(), entry.getKey());
4818 }
4819 }
4820
4821 private void updateShortcutsAndWidgetsPerUser(ArrayList<AppInfo> apps,
4822 final UserHandleCompat user) {
4823 // Create a map of the apps to test against
4824 final HashMap<ComponentName, AppInfo> appsMap = new HashMap<ComponentName, AppInfo>();
4825 final HashSet<String> pkgNames = new HashSet<String>();
4826 for (AppInfo ai : apps) {
4827 appsMap.put(ai.componentName, ai);
4828 pkgNames.add(ai.componentName.getPackageName());
4829 }
4830 final HashSet<ComponentName> iconsToRemove = new HashSet<ComponentName>();
4831
4832 mapOverItems(MAP_RECURSE, new ItemOperator() {
4833 @Override
4834 public boolean evaluate(ItemInfo info, View v, View parent) {
4835 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
4836 ShortcutInfo shortcutInfo = (ShortcutInfo) info;
4837 ComponentName cn = shortcutInfo.getTargetComponent();
4838 AppInfo appInfo = appsMap.get(cn);
4839 if (user.equals(shortcutInfo.user) && cn != null
4840 && LauncherModel.isShortcutInfoUpdateable(info)
4841 && pkgNames.contains(cn.getPackageName())) {
4842 boolean promiseStateChanged = false;
4843 boolean infoUpdated = false;
4844 if (shortcutInfo.isPromise()) {
4845 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
4846 // Auto install icon
4847 PackageManager pm = getContext().getPackageManager();
4848 ResolveInfo matched = pm.resolveActivity(
4849 new Intent(Intent.ACTION_MAIN)
4850 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
4851 PackageManager.MATCH_DEFAULT_ONLY);
4852 if (matched == null) {
4853 // Try to find the best match activity.
4854 Intent intent = pm.getLaunchIntentForPackage(
4855 cn.getPackageName());
4856 if (intent != null) {
4857 cn = intent.getComponent();
4858 appInfo = appsMap.get(cn);
4859 }
4860
4861 if ((intent == null) || (appsMap == null)) {
4862 // Could not find a default activity. Remove this item.
4863 iconsToRemove.add(shortcutInfo.getTargetComponent());
4864
4865 // process next shortcut.
4866 return false;
4867 }
4868 shortcutInfo.promisedIntent = intent;
4869 }
4870 }
4871
4872 // Restore the shortcut.
4873 shortcutInfo.intent = shortcutInfo.promisedIntent;
4874 shortcutInfo.promisedIntent = null;
4875 shortcutInfo.status &= ~ShortcutInfo.FLAG_RESTORED_ICON
4876 & ~ShortcutInfo.FLAG_AUTOINTALL_ICON
4877 & ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
4878
4879 promiseStateChanged = true;
4880 infoUpdated = true;
4881 shortcutInfo.updateIcon(mIconCache);
4882 LauncherModel.updateItemInDatabase(getContext(), shortcutInfo);
4883 }
4884
4885
4886 if (appInfo != null) {
4887 shortcutInfo.updateIcon(mIconCache);
4888 shortcutInfo.title = appInfo.title.toString();
4889 shortcutInfo.contentDescription = appInfo.contentDescription;
4890 infoUpdated = true;
4891 }
4892
4893 if (infoUpdated) {
4894 BubbleTextView shortcut = (BubbleTextView) v;
4895 shortcut.applyFromShortcutInfo(shortcutInfo,
4896 mIconCache, true, promiseStateChanged);
4897
4898 if (parent != null) {
4899 parent.invalidate();
4900 }
4901 }
4902 }
4903 }
4904 // process all the shortcuts
4905 return false;
4906 }
4907 });
4908
4909 if (!iconsToRemove.isEmpty()) {
4910 removeItemsByComponentName(iconsToRemove, user);
4911 }
4912 if (user.equals(UserHandleCompat.myUserHandle())) {
4913 restorePendingWidgets(pkgNames);
4914 }
4915 }
4916
4917 public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
4918 ArrayList<String> packages = new ArrayList<String>(1);
4919 packages.add(packageName);
4920 LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
4921 removeItemsByPackageName(packages, user);
4922 }
4923
4924 public void updatePackageBadge(final String packageName, final UserHandleCompat user) {
4925 mapOverItems(MAP_RECURSE, new ItemOperator() {
4926 @Override
4927 public boolean evaluate(ItemInfo info, View v, View parent) {
4928 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
4929 ShortcutInfo shortcutInfo = (ShortcutInfo) info;
4930 ComponentName cn = shortcutInfo.getTargetComponent();
4931 if (user.equals(shortcutInfo.user) && cn != null
4932 && shortcutInfo.isPromise()
4933 && packageName.equals(cn.getPackageName())) {
4934 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
4935 // For auto install apps update the icon as well as label.
4936 mIconCache.getTitleAndIcon(shortcutInfo,
4937 shortcutInfo.promisedIntent, user, true);
4938 } else {
4939 // Only update the icon for restored apps.
4940 shortcutInfo.updateIcon(mIconCache);
4941 }
4942 BubbleTextView shortcut = (BubbleTextView) v;
4943 shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, false);
4944
4945 if (parent != null) {
4946 parent.invalidate();
4947 }
4948 }
4949 }
4950 // process all the shortcuts
4951 return false;
4952 }
4953 });
4954 }
4955
4956 public void updatePackageState(ArrayList<PackageInstallInfo> installInfos) {
4957 HashSet<String> completedPackages = new HashSet<String>();
4958
4959 for (final PackageInstallInfo installInfo : installInfos) {
4960 mapOverItems(MAP_RECURSE, new ItemOperator() {
4961 @Override
4962 public boolean evaluate(ItemInfo info, View v, View parent) {
4963 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
4964 ShortcutInfo si = (ShortcutInfo) info;
4965 ComponentName cn = si.getTargetComponent();
4966 if (si.isPromise() && (cn != null)
4967 && installInfo.packageName.equals(cn.getPackageName())) {
4968 si.setInstallProgress(installInfo.progress);
4969 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
4970 // Mark this info as broken.
4971 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
4972 }
4973 ((BubbleTextView)v).applyState(false);
4974 }
4975 } else if (v instanceof PendingAppWidgetHostView
4976 && info instanceof LauncherAppWidgetInfo
4977 && ((LauncherAppWidgetInfo) info).providerName.getPackageName()
4978 .equals(installInfo.packageName)) {
4979 ((LauncherAppWidgetInfo) info).installProgress = installInfo.progress;
4980 ((PendingAppWidgetHostView) v).applyState();
4981 }
4982
4983 // process all the shortcuts
4984 return false;
4985 }
4986 });
4987
4988 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
4989 completedPackages.add(installInfo.packageName);
4990 }
4991 }
4992
4993 // Note that package states are sent only for myUser
4994 if (!completedPackages.isEmpty()) {
4995 restorePendingWidgets(completedPackages);
4996 }
4997 }
4998
4999 private void restorePendingWidgets(final Set<String> installedPackaged) {
5000 final ArrayList<LauncherAppWidgetInfo> changedInfo = new ArrayList<LauncherAppWidgetInfo>();
5001
5002 // Iterate non recursively as widgets can't be inside a folder.
5003 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
5004
5005 @Override
5006 public boolean evaluate(ItemInfo info, View v, View parent) {
5007 if (info instanceof LauncherAppWidgetInfo) {
5008 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
5009 if (widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
5010 && installedPackaged.contains(widgetInfo.providerName.getPackageName())) {
5011
5012 changedInfo.add(widgetInfo);
5013
5014 // Remove the provider not ready flag
5015 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
5016 LauncherModel.updateItemInDatabase(getContext(), widgetInfo);
5017 }
5018 }
5019 // process all the widget
5020 return false;
5021 }
5022 });
5023 if (!changedInfo.isEmpty()) {
5024 DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
5025 mLauncher.getAppWidgetHost());
5026 if (LauncherModel.findAppWidgetProviderInfoWithComponent(getContext(),
5027 changedInfo.get(0).providerName) != null) {
5028 // Re-inflate the widgets which have changed status
5029 widgetRefresh.run();
5030 } else {
5031 // widgetRefresh will automatically run when the packages are updated.
5032 }
5033 }
5034 }
5035
5036 private void moveToScreen(int page, boolean animate) {
5037 if (!workspaceInModalState()) {
5038 if (animate) {
5039 snapToPage(page);
5040 } else {
5041 setCurrentPage(page);
5042 }
5043 }
5044 View child = getChildAt(page);
5045 if (child != null) {
5046 child.requestFocus();
5047 }
5048 }
5049
5050 void moveToDefaultScreen(boolean animate) {
5051 moveToScreen(mDefaultPage, animate);
5052 }
5053
5054 void moveToCustomContentScreen(boolean animate) {
5055 if (hasCustomContent()) {
5056 int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
5057 if (animate) {
5058 snapToPage(ccIndex);
5059 } else {
5060 setCurrentPage(ccIndex);
5061 }
5062 View child = getChildAt(ccIndex);
5063 if (child != null) {
5064 child.requestFocus();
5065 }
5066 }
5067 exitWidgetResizeMode();
5068 }
5069
5070 @Override
5071 protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
5072 long screenId = getScreenIdForPageIndex(pageIndex);
5073 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
5074 int count = mScreenOrder.size() - numCustomPages();
5075 if (count > 1) {
5076 return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
5077 R.drawable.ic_pageindicator_add);
5078 }
5079 }
5080
5081 return super.getPageIndicatorMarker(pageIndex);
5082 }
5083
5084 @Override
5085 public void syncPages() {
5086 }
5087
5088 @Override
5089 public void syncPageItems(int page, boolean immediate) {
5090 }
5091
5092 protected String getPageIndicatorDescription() {
5093 String settings = getResources().getString(R.string.settings_button_text);
5094 return getCurrentPageDescription() + ", " + settings;
5095 }
5096
5097 protected String getCurrentPageDescription() {
5098 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
5099 int delta = numCustomPages();
5100 if (hasCustomContent() && getNextPage() == 0) {
5101 return mCustomContentDescription;
5102 }
5103 return String.format(getContext().getString(R.string.workspace_scroll_format),
5104 page + 1 - delta, getChildCount() - delta);
5105 }
5106
5107 public void getLocationInDragLayer(int[] loc) {
5108 mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
5109 }
5110
5111 /**
5112 * Used as a workaround to ensure that the AppWidgetService receives the
5113 * PACKAGE_ADDED broadcast before updating widgets.
5114 */
5115 private class DeferredWidgetRefresh implements Runnable {
5116 private final ArrayList<LauncherAppWidgetInfo> mInfos;
5117 private final LauncherAppWidgetHost mHost;
5118 private final Handler mHandler;
5119
5120 private boolean mRefreshPending;
5121
5122 public DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
5123 LauncherAppWidgetHost host) {
5124 mInfos = infos;
5125 mHost = host;
5126 mHandler = new Handler();
5127 mRefreshPending = true;
5128
5129 mHost.addProviderChangeListener(this);
5130 // Force refresh after 10 seconds, if we don't get the provider changed event.
5131 // This could happen when the provider is no longer available in the app.
5132 mHandler.postDelayed(this, 10000);
5133 }
5134
5135 @Override
5136 public void run() {
5137 mHost.removeProviderChangeListener(this);
5138 mHandler.removeCallbacks(this);
5139
5140 if (!mRefreshPending) {
5141 return;
5142 }
5143
5144 mRefreshPending = false;
5145
5146 for (LauncherAppWidgetInfo info : mInfos) {
5147 if (info.hostView instanceof PendingAppWidgetHostView) {
5148 PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView;
5149 mLauncher.removeAppWidget(info);
5150
5151 CellLayout cl = (CellLayout) view.getParent().getParent();
5152 // Remove the current widget
5153 cl.removeView(view);
5154 mLauncher.bindAppWidget(info);
5155 }
5156 }
5157 }
5158 }
5159 }
|
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.launcher3;
17
18 import android.animation.Animator.AnimatorListener;
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.LayoutTransition;
23 import android.animation.ObjectAnimator;
24 import android.animation.PropertyValuesHolder;
25 import android.animation.TimeInterpolator;
26 import android.animation.ValueAnimator.AnimatorUpdateListener;
27 import android.animation.ValueAnimator;
28 import android.app.WallpaperManager;
29 import android.appwidget.AppWidgetHostView;
30 import android.appwidget.AppWidgetProviderInfo;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.SharedPreferences;
35 import android.content.pm.PackageManager;
36 import android.content.pm.ResolveInfo;
37 import android.content.res.Resources;
38 import android.content.res.TypedArray;
39 import android.graphics.Bitmap;
40 import android.graphics.Canvas;
41 import android.graphics.Matrix;
42 import android.graphics.Paint;
43 import android.graphics.Point;
44 import android.graphics.PointF;
45 import android.graphics.Rect;
46 import android.graphics.Region.Op;
47 import android.graphics.drawable.Drawable;
48 import android.net.Uri;
49 import android.os.AsyncTask;
50 import android.os.Handler;
51 import android.os.IBinder;
52 import android.os.Parcelable;
53 import android.support.v4.view.ViewCompat;
54 import android.util.AttributeSet;
55 import android.util.Log;
56 import android.util.SparseArray;
57 import android.view.Choreographer;
58 import android.view.Display;
59 import android.view.MotionEvent;
60 import android.view.View;
61 import android.view.ViewGroup;
62 import android.view.accessibility.AccessibilityManager;
63 import android.view.animation.DecelerateInterpolator;
64 import android.view.animation.Interpolator;
65 import android.widget.TextView;
66 import com.android.launcher3.FolderIcon.FolderRingAnimator;
67 import com.android.launcher3.Launcher.CustomContentCallbacks;
68 import com.android.launcher3.LauncherSettings.Favorites;
69 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
70 import com.android.launcher3.compat.PackageInstallerCompat;
71 import com.android.launcher3.compat.UserHandleCompat;
72 import java.util.ArrayList;
73 import java.util.HashMap;
74 import java.util.HashSet;
75 import java.util.Iterator;
76 import java.util.Map;
77 import java.util.Set;
78 import java.util.concurrent.atomic.AtomicInteger;
79
80
81 /**
82 * The workspace is a wide area with a wallpaper and a finite number of pages.
83 * Each page contains a number of icons, folders or widgets the user can
84 * interact with. A workspace is meant to be used with a fixed width only.
85 */
86 public class Workspace extends SmoothPagedView implements DropTarget , DragSource , DragScroller , View.O🔵
87 private static final String TAG = "Launcher.Workspace";
88
89 // Y rotation to apply to the workspace screens
90 // Y rotation to apply to the workspace screens
91 private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
92
93 private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
94
95 private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
96
97 private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
98
99 protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
100
101 protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
102
103 private static final int BACKGROUND_FADE_OUT_DURATION = 350;
104
105 private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
106
107 private static final int FLING_THRESHOLD_VELOCITY = 500;
108
109 private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
110
111 static final boolean MAP_NO_RECURSE = false;
112
113 static final boolean MAP_RECURSE = true;
114
115 // These animators are used to fade the children's outlines
116 // These animators are used to fade the children's outlines
117 private ObjectAnimator mChildrenOutlineFadeInAnimation;
118
119 private ObjectAnimator mChildrenOutlineFadeOutAnimation;
120
121 private float mChildrenOutlineAlpha = 0;
122
123 // These properties refer to the background protection gradient used for AllApps and Customize
124 // These properties refer to the background protection gradient used for AllApps and Customize
125 private ValueAnimator mBackgroundFadeInAnimation;
126
127 private ValueAnimator mBackgroundFadeOutAnimation;
128
129 private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
130
131 private long mTouchDownTime = -1;
132
133 private long mCustomContentShowTime = -1;
134
135 private LayoutTransition mLayoutTransition;
136
137 private final WallpaperManager mWallpaperManager;
138
139 private IBinder mWindowToken;
140
141 private int mOriginalDefaultPage;
142
143 private int mDefaultPage;
144
145 private ShortcutAndWidgetContainer mDragSourceInternal;
146
147 private static boolean sAccessibilityEnabled;
148
149 // The screen id used for the empty screen always present to the right.
150 static final long EXTRA_EMPTY_SCREEN_ID = -201;
151
152 private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
153
154 private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
155
156 private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
157
158 private Runnable mRemoveEmptyScreenRunnable;
159
160 private boolean mDeferRemoveExtraEmptyScreen = false;
161
162 /**
163 * CellInfo for the cell that is currently being dragged
164 */
165 private CellLayout.CellInfo mDragInfo;
166
167 /**
168 * Target drop area calculated during last acceptDrop call.
169 */
170 private int[] mTargetCell = new int[2];
171
172 private int mDragOverX = -1;
173
174 private int mDragOverY = -1;
175
176 static Rect mLandscapeCellLayoutMetrics = null;
177
178 static Rect mPortraitCellLayoutMetrics = null;
179
180 CustomContentCallbacks mCustomContentCallbacks;
181
182 boolean mCustomContentShowing;
183
184 private float mLastCustomContentScrollProgress = -1f;
185
186 private String mCustomContentDescription = "";
187
188 /**
189 * The CellLayout that is currently being dragged over
190 */
191 private CellLayout mDragTargetLayout = null;
192
193 /**
194 * The CellLayout that we will show as glowing
195 */
196 private CellLayout mDragOverlappingLayout = null;
197
198 /**
199 * The CellLayout which will be dropped to
200 */
201 private CellLayout mDropToLayout = null;
202
203 private Launcher mLauncher;
204
205 private IconCache mIconCache;
206
207 private DragController mDragController;
208
209 // These are temporary variables to prevent having to allocate a new object just to
210 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
211 private int[] mTempCell = new int[2];
212
213 private int[] mTempPt = new int[2];
214
215 private int[] mTempEstimate = new int[2];
216
217 private float[] mDragViewVisualCenter = new float[2];
218
219 private float[] mTempCellLayoutCenterCoordinates = new float[2];
220
221 private Matrix mTempInverseMatrix = new Matrix();
222
223 private SpringLoadedDragController mSpringLoadedDragController;
224
225 private float mSpringLoadedShrinkFactor;
226
227 private float mOverviewModeShrinkFactor;
228
229 // State variable that indicates whether the pages are small (ie when you're
230 // in all apps or customize mode)
231 enum State {
232
233 NORMAL,
234 NORMAL_HIDDEN,
235 SPRING_LOADED,
236 OVERVIEW,
237 OVERVIEW_HIDDEN;}
238
239 private State mState = State.NORMAL;
240
241 private boolean mIsSwitchingState = false;
242
243 boolean mAnimatingViewIntoPlace = false;
244
245 boolean mIsDragOccuring = false;
246
247 boolean mChildrenLayersEnabled = true;
248
249 private boolean mStripScreensOnPageStopMoving = false;
250
251 /** Is the user is dragging an item near the edge of a page? */
252 private boolean mInScrollArea = false;
253
254 private HolographicOutlineHelper mOutlineHelper;
255
256 private Bitmap mDragOutline = null;
257
258 private static final Rect sTempRect = new Rect();
259
260 private final int[] mTempXY = new int[2];
261
262 private int[] mTempVisiblePagesRange = new int[2];
263
264 private boolean mOverscrollEffectSet;
265
266 public static final int DRAG_BITMAP_PADDING = 2;
267
268 private boolean mWorkspaceFadeInAdjacentScreens;
269
270 WallpaperOffsetInterpolator mWallpaperOffset;
271
272 private boolean mWallpaperIsLiveWallpaper;
273
274 private int mNumPagesForWallpaperParallax;
275
276 private float mLastSetWallpaperOffsetSteps = 0;
277
278 private Runnable mDelayedResizeRunnable;
279
280 private Runnable mDelayedSnapToPageRunnable;
281
282 private Point mDisplaySize = new Point();
283
284 private int mCameraDistance;
285
286 // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
287 // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
288 private static final int FOLDER_CREATION_TIMEOUT = 0;
289
290 public static final int REORDER_TIMEOUT = 350;
291
292 private final Alarm mFolderCreationAlarm = new Alarm();
293
294 private final Alarm mReorderAlarm = new Alarm();
295
296 private FolderRingAnimator mDragFolderRingAnimator = null;
297
298 private FolderIcon mDragOverFolderIcon = null;
299
300 private boolean mCreateUserFolderOnDrop = false;
301
302 private boolean mAddToExistingFolderOnDrop = false;
303
304 private DropTarget.DragEnforcer mDragEnforcer;
305
306 private float mMaxDistanceForFolderCreation;
307
308 private final Canvas mCanvas = new Canvas();
309
310 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
311 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
312 private float mXDown;
313
314 private float mYDown;
315
316 final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
317
318 final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
319
320 final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
321
322 // Relating to the animation of items being dropped externally
323 // Relating to the animation of items being dropped externally
324 public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
325
326 public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
327
328 public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
329
330 public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
331
332 public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
333
334 // Related to dragging, folder creation and reordering
335 private static final int DRAG_MODE_NONE = 0;
336
337 private static final int DRAG_MODE_CREATE_FOLDER = 1;
338
339 private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
340
341 private static final int DRAG_MODE_REORDER = 3;
342
343 private int mDragMode = DRAG_MODE_NONE;
344
345 private int mLastReorderX = -1;
346
347 private int mLastReorderY = -1;
348
349 private SparseArray<Parcelable> mSavedStates;
350
351 private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
352
353 // These variables are used for storing the initial and final values during workspace animations
354 // These variables are used for storing the initial and final values during workspace animations
355 private int mSavedScrollX;
356
357 private float mSavedRotationY;
358
359 private float mSavedTranslationX;
360
361 private float mCurrentScale;
362
363 private float mNewScale;
364
365 private float[] mOldBackgroundAlphas;
366
367 private float[] mOldAlphas;
368
369 private float[] mNewBackgroundAlphas;
370
371 private float[] mNewAlphas;
372
373 private int mLastChildCount = -1;
374
375 private float mTransitionProgress;
376
377 float mOverScrollEffect = 0f;
378
379 private Runnable mDeferredAction;
380
381 private boolean mDeferDropAfterUninstall;
382
383 private boolean mUninstallSuccessful;
384
385 private final Runnable mBindPages = new Runnable() {
386 @Override
387 public void run() {
388 mLauncher.getModel().bindRemainingSynchronousPages();
389 }
390 };
391
392 /**
393 * Used to inflate the Workspace from XML.
394 *
395 * @param context
396 * The application's context.
397 * @param attrs
398 * The attributes set containing the Workspace's customization values.
399 */
400 public Workspace(Context context, AttributeSet attrs) {
401 this(context, attrs, 0);
402 }
403
404 /**
405 * Used to inflate the Workspace from XML.
406 *
407 * @param context
408 * The application's context.
409 * @param attrs
410 * The attributes set containing the Workspace's customization values.
411 * @param defStyle
412 * Unused.
413 */
414 public Workspace(Context context, AttributeSet attrs, int defStyle) {
415 super(context, attrs, defStyle);
416 mContentIsRefreshable = false;
417 mOutlineHelper = HolographicOutlineHelper.obtain(context);
418 mDragEnforcer = new DropTarget.DragEnforcer(context);
419 // With workspace, data is available straight from the get-go
420 setDataIsReady();
421 mLauncher = ((Launcher) (context));
422 final Resources res = getResources();
423 mWorkspaceFadeInAdjacentScreens = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfil🔵
424 mFadeInAdjacentScreens = false;
425 mWallpaperManager = WallpaperManager.getInstance(context);
426 LauncherAppState app = LauncherAppState.getInstance();
427 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
428 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
429 mSpringLoadedShrinkFactor = res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) 🔵
430 mOverviewModeShrinkFactor = grid.getOverviewModeScale();
431 mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
432 mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
433 a.recycle();
434 setOnHierarchyChangeListener(this);
435 setHapticFeedbackEnabled(false);
436 initWorkspace();
437 // Disable multitouch across the workspace/all apps/customize tray
438 setMotionEventSplittingEnabled(true);
439 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
440 }
441
442 @Override
443 public void setInsets(Rect insets) {
444 mInsets.set(insets);
445
446 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
447 if (customScreen != null) {
448 View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0);
449 if (customContent instanceof Insettable) {
450 ((Insettable) customContent).setInsets(mInsets);
451 }
452 }
453 }
454
455 // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
456 // dimension if unsuccessful
457 public int[] estimateItemSize(int hSpan, int vSpan,
458 ItemInfo itemInfo, boolean springLoaded) {
459 int[] size = new int[2];
460 if (getChildCount() > 0) {
461 // Use the first non-custom page to estimate the child position
462 CellLayout cl = (CellLayout) getChildAt(numCustomPages());
463 Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
464 size[0] = r.width();
465 size[1] = r.height();
466 if (springLoaded) {
467 size[0] *= mSpringLoadedShrinkFactor;
468 size[1] *= mSpringLoadedShrinkFactor;
469 }
470 return size;
471 } else {
472 size[0] = Integer.MAX_VALUE;
473 size[1] = Integer.MAX_VALUE;
474 return size;
475 }
476 }
477
478 public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
479 int hCell, int vCell, int hSpan, int vSpan) {
480 Rect r = new Rect();
481 cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
482 return r;
483 }
484
485 public void onDragStart(final DragSource source, Object info, int dragAction) {
486 mIsDragOccuring = true;
487 updateChildrenLayersEnabled(false);
488 mLauncher.lockScreenOrientation();
489 mLauncher.onInteractionBegin();
490 setChildrenBackgroundAlphaMultipliers(1.0F);
491 // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
492 InstallShortcutReceiver.enableInstallQueue();
493 UninstallShortcutReceiver.enableUninstallQueue();
494 post(new Runnable() {
495 @Override
496 public void run() {
497 if (mIsDragOccuring) {
498 mDeferRemoveExtraEmptyScreen = false;
499 addExtraEmptyScreenOnDrag();
500 }
501 }
502 });
503 }
504
505 public void deferRemoveExtraEmptyScreen() {
506 mDeferRemoveExtraEmptyScreen = true;
507 }
508
509 public void onDragEnd() {
510 if (!mDeferRemoveExtraEmptyScreen) {
511 removeExtraEmptyScreen(true, mDragSourceInternal != null);
512 }
513 mIsDragOccuring = false;
514 updateChildrenLayersEnabled(false);
515 mLauncher.unlockScreenOrientation(false);
516 // Re-enable any Un/InstallShortcutReceiver and now process any queued items
517 InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
518 UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
519 mDragSourceInternal = null;
520 mLauncher.onInteractionEnd();
521 }
522
523 /**
524 * Initializes various states for this workspace.
525 */
526 protected void initWorkspace() {
527 mCurrentPage = mDefaultPage;
528 Launcher.setScreen(mCurrentPage);
529 LauncherAppState app = LauncherAppState.getInstance();
530 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
531 mIconCache = app.getIconCache();
532 setWillNotDraw(false);
533 setClipChildren(false);
534 setClipToPadding(false);
535 setChildrenDrawnWithCacheEnabled(true);
536 setMinScale(mOverviewModeShrinkFactor);
537 setupLayoutTransition();
538 mWallpaperOffset = new WallpaperOffsetInterpolator();
539 Display display = mLauncher.getWindowManager().getDefaultDisplay();
540 display.getSize(mDisplaySize);
541 mMaxDistanceForFolderCreation = 0.55F * grid.iconSizePx;
542 mFlingThresholdVelocity = ((int) (FLING_THRESHOLD_VELOCITY * mDensity));
543 // Set the wallpaper dimensions when Launcher starts up
544 setWallpaperDimension();
545 }
546
547 private void setupLayoutTransition() {
548 // We want to show layout transitions when pages are deleted, to close the gap.
549 mLayoutTransition = new LayoutTransition();
550 mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
551 mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
552 mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
553 mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
554 setLayoutTransition(mLayoutTransition);
555 }
556
557 void enableLayoutTransitions() {
558 setLayoutTransition(mLayoutTransition);
559 }
560
561 void disableLayoutTransitions() {
562 setLayoutTransition(null);
563 }
564
565 @Override
566 protected int getScrollMode() {
567 return SmoothPagedView.X_LARGE_MODE;
568 }
569
570 @Override
571 public void onChildViewAdded(View parent, View child) {
572 if (!(child instanceof CellLayout)) {
573 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
574 }
575 CellLayout cl = ((CellLayout) child);
576 cl.setOnInterceptTouchListener(this);
577 cl.setClickable(true);
578 cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
579 super.onChildViewAdded(parent, child);
580 }
581
582 protected boolean shouldDrawChild(View child) {
583 final CellLayout cl = (CellLayout) child;
584 return super.shouldDrawChild(child) &&
585 (mIsSwitchingState ||
586 cl.getShortcutsAndWidgets().getAlpha() > 0 ||
587 cl.getBackgroundAlpha() > 0);
588 }
589
590 /**
591 * @return The open folder on the current screen, or null if there is none
592 */
593 Folder getOpenFolder() {
594 DragLayer dragLayer = mLauncher.getDragLayer();
595 int count = dragLayer.getChildCount();
596 for (int i = 0; i < count; i++) {
597 View child = dragLayer.getChildAt(i);
598 if (child instanceof Folder) {
599 Folder folder = (Folder) child;
600 if (folder.getInfo().opened)
601 return folder;
602 }
603 }
604 return null;
605 }
606
607 boolean isTouchActive() {
608 return mTouchState != TOUCH_STATE_REST;
609 }
610
611 public void removeAllWorkspaceScreens() {
612 // Disable all layout transitions before removing all pages to ensure that we don't get the
613 // transition animations competing with us changing the scroll when we add pages or the
614 // custom content screen
615 disableLayoutTransitions();
616
617 // Since we increment the current page when we call addCustomContentPage via bindScreens
618 // (and other places), we need to adjust the current page back when we clear the pages
619 if (hasCustomContent()) {
620 removeCustomContentPage();
621 }
622
623 // Remove the pages and clear the screen models
624 removeAllViews();
625 mScreenOrder.clear();
626 mWorkspaceScreens.clear();
627
628 // Re-enable the layout transitions
629 enableLayoutTransitions();
630 }
631
632 public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
633 // Find the index to insert this view into. If the empty screen exists, then
634 // insert it before that.
635 int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
636 if (insertIndex < 0) {
637 insertIndex = mScreenOrder.size();
638 }
639 return insertNewWorkspaceScreen(screenId, insertIndex);
640 }
641
642 public long insertNewWorkspaceScreen(long screenId) {
643 return insertNewWorkspaceScreen(screenId, getChildCount());
644 }
645
646 public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
647 // Log to disk
648 Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId +
649 " at index: " + insertIndex, true);
650
651 if (mWorkspaceScreens.containsKey(screenId)) {
652 throw new RuntimeException("Screen id " + screenId + " already exists!");
653 }
654
655 CellLayout newScreen = (CellLayout)
656 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
657
658 newScreen.setOnLongClickListener(mLongClickListener);
659 newScreen.setOnClickListener(mLauncher);
660 newScreen.setSoundEffectsEnabled(false);
661 mWorkspaceScreens.put(screenId, newScreen);
662 mScreenOrder.add(insertIndex, screenId);
663 addView(newScreen, insertIndex);
664 return screenId;
665 }
666
667 public void createCustomContentContainer() {
668 CellLayout customScreen = ((CellLayout) (mLauncher.getLayoutInflater().inflate(R.layout.workspace🔵
669 customScreen.disableBackground();
670 customScreen.disableDragTarget();
671 mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
672 mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
673 // We want no padding on the custom content
674 customScreen.setPadding(0, 0, 0, 0);
675 addFullScreenPage(customScreen);
676 // Ensure that the current page and default page are maintained.
677 mDefaultPage = mOriginalDefaultPage + 1;
678 // Update the custom content hint
679 if (mRestorePage != INVALID_RESTORE_PAGE) {
680 mRestorePage = mRestorePage + 1;
681 } else {
682 setCurrentPage(getCurrentPage() + 1);
683 }
684 }
685
686 public void removeCustomContentPage() {
687 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
688 if (customScreen == null) {
689 throw new RuntimeException("Expected custom content screen to exist");
690 }
691 mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID);
692 mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID);
693 removeView(customScreen);
694 if (mCustomContentCallbacks != null) {
695 mCustomContentCallbacks.onScrollProgressChanged(0);
696 mCustomContentCallbacks.onHide();
697 }
698 mCustomContentCallbacks = null;
699 // Ensure that the current page and default page are maintained.
700 mDefaultPage = mOriginalDefaultPage - 1;
701 // Update the custom content hint
702 if (mRestorePage != INVALID_RESTORE_PAGE) {
703 mRestorePage = mRestorePage - 1;
704 } else {
705 setCurrentPage(getCurrentPage() - 1);
706 }
707 }
708
709 public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
710 String description) {
711 if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
712 throw new RuntimeException("Expected custom content screen to exist");
713 }
714
715 // Add the custom content to the full screen custom page
716 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
717 int spanX = customScreen.getCountX();
718 int spanY = customScreen.getCountY();
719 CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
720 lp.canReorder = false;
721 lp.isFullscreen = true;
722 if (customContent instanceof Insettable) {
723 ((Insettable)customContent).setInsets(mInsets);
724 }
725
726 // Verify that the child is removed from any existing parent.
727 if (customContent.getParent() instanceof ViewGroup) {
728 ViewGroup parent = (ViewGroup) customContent.getParent();
729 parent.removeView(customContent);
730 }
731 customScreen.removeAllViews();
732 customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
733 mCustomContentDescription = description;
734
735 mCustomContentCallbacks = callbacks;
736 }
737
738 public void addExtraEmptyScreenOnDrag() {
739 // Log to disk
740 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreenOnDrag()", true);
741
742 boolean lastChildOnScreen = false;
743 boolean childOnFinalScreen = false;
744
745 // Cancel any pending removal of empty screen
746 mRemoveEmptyScreenRunnable = null;
747
748 if (mDragSourceInternal != null) {
749 if (mDragSourceInternal.getChildCount() == 1) {
750 lastChildOnScreen = true;
751 }
752 CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
753 if (indexOfChild(cl) == getChildCount() - 1) {
754 childOnFinalScreen = true;
755 }
756 }
757
758 // If this is the last item on the final screen
759 if (lastChildOnScreen && childOnFinalScreen) {
760 return;
761 }
762 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
763 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
764 }
765 }
766
767 public boolean addExtraEmptyScreen() {
768 // Log to disk
769 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreen()", true);
770
771 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
772 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
773 return true;
774 }
775 return false;
776 }
777
778 private void convertFinalScreenToEmptyScreenIfNecessary() {
779 // Log to disk
780 Launcher.addDumpLog(TAG, "11683562 - convertFinalScreenToEmptyScreenIfNecessary()", true);
781 if (mLauncher.isWorkspaceLoading()) {
782 // Invalid and dangerous operation if workspace is loading
783 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
784 return;
785 }
786 if (hasExtraEmptyScreen() || (mScreenOrder.size() == 0)) {
787 return;
788 }
789 long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
790 if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) {
791 return;
792 }
793 CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
794 // If the final screen is empty, convert it to the extra empty screen
795 if ((finalScreen.getShortcutsAndWidgets().getChildCount() == 0) && (!finalScreen.isDropPending())🔵
796 mWorkspaceScreens.remove(finalScreenId);
797 mScreenOrder.remove(finalScreenId);
798 // if this is the last non-custom content screen, convert it to the empty screen
799 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
800 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
801 // Update the model if we have changed any screens
802 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
803 Launcher.addDumpLog(TAG, "11683562 - extra empty screen: " + finalScreenId, true);
804 }
805 }
806
807 public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
808 removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
809 }
810
811 public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete, final int🔵
812 // Log to disk
813 Launcher.addDumpLog(TAG, "11683562 - removeExtraEmptyScreen()", true);
814 if (mLauncher.isWorkspaceLoading()) {
815 // Don't strip empty screens if the workspace is still loading
816 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
817 return;
818 }
819 if (delay > 0) {
820 postDelayed(new Runnable() {
821 @Override
822 public void run() {
823 removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
824 }
825 }, delay);
826 return;
827 }
828 convertFinalScreenToEmptyScreenIfNecessary();
829 if (hasExtraEmptyScreen()) {
830 int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
831 if (getNextPage() == emptyIndex) {
832 snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
833 fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION, onCo🔵
834 } else {
835 fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION, onComplete, stripEmptyScreens);
836 }
837 return;
838 } else if (stripEmptyScreens) {
839 // If we're not going to strip the empty screens after removing
840 // the extra empty screen, do it right away.
841 stripEmptyScreens();
842 }
843 if (onComplete != null) {
844 onComplete.run();
845 }
846 }
847
848 private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
849 final boolean stripEmptyScreens) {
850 // Log to disk
851 // XXX: Do we need to update LM workspace screens below?
852 Launcher.addDumpLog(TAG, "11683562 - fadeAndRemoveEmptyScreen()", true);
853 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
854 PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
855
856 final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
857
858 mRemoveEmptyScreenRunnable = new Runnable() {
859 @Override
860 public void run() {
861 if (hasExtraEmptyScreen()) {
862 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
863 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
864 removeView(cl);
865 if (stripEmptyScreens) {
866 stripEmptyScreens();
867 }
868 }
869 }
870 };
871
872 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
873 oa.setDuration(duration);
874 oa.setStartDelay(delay);
875 oa.addListener(new AnimatorListenerAdapter() {
876 @Override
877 public void onAnimationEnd(Animator animation) {
878 if (mRemoveEmptyScreenRunnable != null) {
879 mRemoveEmptyScreenRunnable.run();
880 }
881 if (onComplete != null) {
882 onComplete.run();
883 }
884 }
885 });
886 oa.start();
887 }
888
889 public boolean hasExtraEmptyScreen() {
890 int nScreens = getChildCount();
891 nScreens = nScreens - numCustomPages();
892 return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
893 }
894
895 public long commitExtraEmptyScreen() {
896 // Log to disk
897 Launcher.addDumpLog(TAG, "11683562 - commitExtraEmptyScreen()", true);
898 if (mLauncher.isWorkspaceLoading()) {
899 // Invalid and dangerous operation if workspace is loading
900 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
901 return -1;
902 }
903 int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
904 CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
905 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
906 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
907 long newId = LauncherAppState.getLauncherProvider().generateNewScreenId();
908 mWorkspaceScreens.put(newId, cl);
909 mScreenOrder.add(newId);
910 // Update the page indicator marker
911 if (getPageIndicator() != null) {
912 getPageIndicator().updateMarker(index, getPageIndicatorMarker(index));
913 }
914 // Update the model for the new screen
915 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
916 return newId;
917 }
918
919 public CellLayout getScreenWithId(long screenId) {
920 CellLayout layout = mWorkspaceScreens.get(screenId);
921 return layout;
922 }
923
924 public long getIdForScreen(CellLayout layout) {
925 Iterator<Long> iter = mWorkspaceScreens.keySet().iterator();
926 while (iter.hasNext()) {
927 long id = iter.next();
928 if (mWorkspaceScreens.get(id) == layout) {
929 return id;
930 }
931 }
932 return -1;
933 }
934
935 public int getPageIndexForScreenId(long screenId) {
936 return indexOfChild(mWorkspaceScreens.get(screenId));
937 }
938
939 public long getScreenIdForPageIndex(int index) {
940 if (0 <= index && index < mScreenOrder.size()) {
941 return mScreenOrder.get(index);
942 }
943 return -1;
944 }
945
946 ArrayList<Long> getScreenOrder() {
947 return mScreenOrder;
948 }
949
950 public void stripEmptyScreens() {
951 // Log to disk
952 Launcher.addDumpLog(TAG, "11683562 - stripEmptyScreens()", true);
953 if (mLauncher.isWorkspaceLoading()) {
954 // Don't strip empty screens if the workspace is still loading.
955 // This is dangerous and can result in data loss.
956 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
957 return;
958 }
959 if (isPageMoving()) {
960 mStripScreensOnPageStopMoving = true;
961 return;
962 }
963 int currentPage = getNextPage();
964 ArrayList<Long> removeScreens = new ArrayList<Long>();
965 for (Long id : mWorkspaceScreens.keySet()) {
966 CellLayout cl = mWorkspaceScreens.get(id);
967 if ((id >= 0) && (cl.getShortcutsAndWidgets().getChildCount() == 0)) {
968 removeScreens.add(id);
969 }
970 }
971 // We enforce at least one page to add new items to. In the case that we remove the last
972 // such screen, we convert the last screen to the empty screen
973 int minScreens = 1 + numCustomPages();
974 int pageShift = 0;
975 for (Long id : removeScreens) {
976 Launcher.addDumpLog(TAG, "11683562 - removing id: " + id, true);
977 CellLayout cl = mWorkspaceScreens.get(id);
978 mWorkspaceScreens.remove(id);
979 mScreenOrder.remove(id);
980 if (getChildCount() > minScreens) {
981 if (indexOfChild(cl) < currentPage) {
982 pageShift++;
983 }
984 removeView(cl);
985 } else {
986 // if this is the last non-custom content screen, convert it to the empty screen
987 mRemoveEmptyScreenRunnable = null;
988 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
989 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
990 }
991 }
992 if (!removeScreens.isEmpty()) {
993 // Update the model if we have changed any screens
994 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
995 }
996 if (pageShift >= 0) {
997 setCurrentPage(currentPage - pageShift);
998 }
999 }
1000
1001 // See implementation for parameter definition.
1002 void addInScreen(View child, long container, long screenId,
1003 int x, int y, int spanX, int spanY) {
1004 addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
1005 }
1006
1007 // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
1008 // See implementation for parameter definition.
1009 void addInScreenFromBind(View child, long container, long screenId, int x, int y,
1010 int spanX, int spanY) {
1011 addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
1012 }
1013
1014 // See implementation for parameter definition.
1015 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
1016 boolean insert) {
1017 addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
1018 }
1019
1020 /**
1021 * Adds the specified child in the specified screen. The position and dimension of
1022 * the child are defined by x, y, spanX and spanY.
1023 *
1024 * @param child The child to add in one of the workspace's screens.
1025 * @param screenId The screen in which to add the child.
1026 * @param x The X position of the child in the screen's grid.
1027 * @param y The Y position of the child in the screen's grid.
1028 * @param spanX The number of cells spanned horizontally by the child.
1029 * @param spanY The number of cells spanned vertically by the child.
1030 * @param insert When true, the child is inserted at the beginning of the children list.
1031 * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
1032 * the x and y position in which to place hotseat items. Otherwise
1033 * we use the x and y position to compute the rank.
1034 */
1035 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY, boole🔵
1036 if (container == Favorites.CONTAINER_DESKTOP) {
1037 if (getScreenWithId(screenId) == null) {
1038 Log.e(TAG, ("Skipping child, screenId " + screenId) + " not found");
1039 // DEBUGGING - Print out the stack trace to see where we are adding from
1040 new Throwable().printStackTrace();
1041 return;
1042 }
1043 }
1044 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
1045 // This should never happen
1046 throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
1047 }
1048 final CellLayout layout;
1049 if (container == Favorites.CONTAINER_HOTSEAT) {
1050 layout = mLauncher.getHotseat().getLayout();
1051 child.setOnKeyListener(new HotseatIconKeyEventListener());
1052 // Hide folder title in the hotseat
1053 if (child instanceof FolderIcon) {
1054 ((FolderIcon) (child)).setTextVisible(false);
1055 }
1056 if (computeXYFromRank) {
1057 x = mLauncher.getHotseat().getCellXFromOrder(((int) (screenId)));
1058 y = mLauncher.getHotseat().getCellYFromOrder(((int) (screenId)));
1059 } else {
1060 screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
1061 }
1062 } else {
1063 // Show folder title if not in the hotseat
1064 if (child instanceof FolderIcon) {
1065 ((FolderIcon) (child)).setTextVisible(true);
1066 }
1067 layout = getScreenWithId(screenId);
1068 child.setOnKeyListener(new IconKeyEventListener());
1069 }
1070 ViewGroup.LayoutParams genericLp = child.getLayoutParams();
1071 CellLayout.LayoutParams lp;
1072 if ((genericLp == null) || (!(genericLp instanceof CellLayout.LayoutParams))) {
1073 lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
1074 } else {
1075 lp = ((CellLayout.LayoutParams) (genericLp));
1076 lp.cellX = x;
1077 lp.cellY = y;
1078 lp.cellHSpan = spanX;
1079 lp.cellVSpan = spanY;
1080 }
1081 if ((spanX < 0) && (spanY < 0)) {
1082 lp.isLockedToGrid = false;
1083 }
1084 // Get the canonical child id to uniquely represent this view in this screen
1085 ItemInfo info = ((ItemInfo) (child.getTag()));
1086 int childId = mLauncher.getViewIdForItem(info);
1087 boolean markCellsAsOccupied = !(child instanceof Folder);
1088 if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
1089 // TODO: This branch occurs when the workspace is adding views
1090 // outside of the defined grid
1091 // maybe we should be deleting these items from the LauncherModel?
1092 Launcher.addDumpLog(TAG, ((("Failed to add to item at (" + lp.cellX) + ",") + lp.cellY) + ") 🔵
1093 }
1094 if (!(child instanceof Folder)) {
1095 child.setHapticFeedbackEnabled(false);
1096 child.setOnLongClickListener(mLongClickListener);
1097 }
1098 if (child instanceof DropTarget) {
1099 mDragController.addDropTarget(((DropTarget) (child)));
1100 }
1101 }
1102
1103 /**
1104 * Called directly from a CellLayout (not by the framework), after we've been added as a
1105 * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
1106 * that it should intercept touch events, which is not something that is normally supported.
1107 */
1108 @Override
1109 public boolean onTouch(View v, MotionEvent event) {
1110 return (workspaceInModalState() || (!isFinishedSwitchingState())) || ((!workspaceInModalState()) 🔵
1111 }
1112
1113 public boolean isSwitchingState() {
1114 return mIsSwitchingState;
1115 }
1116
1117 /** This differs from isSwitchingState in that we take into account how far the transition
1118 * has completed. */
1119 public boolean isFinishedSwitchingState() {
1120 return !mIsSwitchingState || (mTransitionProgress > 0.5f);
1121 }
1122
1123 protected void onWindowVisibilityChanged (int visibility) {
1124 mLauncher.onWindowVisibilityChanged(visibility);
1125 }
1126
1127 @Override
1128 public boolean dispatchUnhandledMove(View focused, int direction) {
1129 if (workspaceInModalState() || (!isFinishedSwitchingState())) {
1130 // when the home screens are shrunken, shouldn't allow side-scrolling
1131 return false;
1132 }
1133 return super.dispatchUnhandledMove(focused, direction);
1134 }
1135
1136 @Override
1137 public boolean onInterceptTouchEvent(MotionEvent ev) {
1138 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1139 case MotionEvent.ACTION_DOWN :
1140 mXDown = ev.getX();
1141 mYDown = ev.getY();
1142 mTouchDownTime = System.currentTimeMillis();
1143 break;
1144 case MotionEvent.ACTION_POINTER_UP :
1145 case MotionEvent.ACTION_UP :
1146 if (mTouchState == TOUCH_STATE_REST) {
1147 final CellLayout currentPage = ((CellLayout) (getChildAt(mCurrentPage)));
1148 if ((currentPage != null) && (!currentPage.lastDownOnOccupiedCell())) {
1149 onWallpaperTap(ev);
1150 }
1151 }
1152 }
1153 return super.onInterceptTouchEvent(ev);
1154 }
1155
1156 @Override
1157 public boolean onGenericMotionEvent(MotionEvent event) {
1158 // Ignore pointer scroll events if the custom content doesn't allow scrolling.
1159 if (((getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID) && (mCustomContentCa🔵
1160 return false;
1161 }
1162 return super.onGenericMotionEvent(event);
1163 }
1164
1165 protected void reinflateWidgetsIfNecessary() {
1166 final int clCount = getChildCount();
1167 for (int i = 0; i < clCount; i++) {
1168 CellLayout cl = ((CellLayout) (getChildAt(i)));
1169 ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
1170 final int itemCount = swc.getChildCount();
1171 for (int j = 0; j < itemCount; j++) {
1172 View v = swc.getChildAt(j);
1173 if ((v != null) && (v.getTag() instanceof LauncherAppWidgetInfo)) {
1174 LauncherAppWidgetInfo info = ((LauncherAppWidgetInfo) (v.getTag()));
1175 LauncherAppWidgetHostView lahv = ((LauncherAppWidgetHostView) (info.hostView));
1176 if ((lahv != null) && lahv.isReinflateRequired()) {
1177 mLauncher.removeAppWidget(info);
1178 // Remove the current widget which is inflated with the wrong orientation
1179 cl.removeView(lahv);
1180 mLauncher.bindAppWidget(info);
1181 }
1182 }
1183 }
1184 }
1185 }
1186
1187 @Override
1188 protected void determineScrollingStart(MotionEvent ev) {
1189 if (!isFinishedSwitchingState()) {
1190 return;
1191 }
1192 float deltaX = ev.getX() - mXDown;
1193 float absDeltaX = Math.abs(deltaX);
1194 float absDeltaY = Math.abs(ev.getY() - mYDown);
1195 if (Float.compare(absDeltaX, 0.0F) == 0) {
1196 return;
1197 }
1198 float slope = absDeltaY / absDeltaX;
1199 float theta = ((float) (Math.atan(slope)));
1200 if ((absDeltaX > mTouchSlop) || (absDeltaY > mTouchSlop)) {
1201 cancelCurrentPageLongPress();
1202 }
1203 boolean passRightSwipesToCustomContent = (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTE🔵
1204 boolean swipeInIgnoreDirection = (isLayoutRtl()) ? deltaX < 0 : deltaX > 0;
1205 boolean onCustomContentScreen = getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREE🔵
1206 if ((swipeInIgnoreDirection && onCustomContentScreen) && passRightSwipesToCustomContent) {
1207 // Pass swipes to the right to the custom content page.
1208 return;
1209 }
1210 if ((onCustomContentScreen && (mCustomContentCallbacks != null)) && (!mCustomContentCallbacks.isS🔵
1211 // Don't allow workspace scrolling if the current custom content screen doesn't allow
1212 // scrolling.
1213 return;
1214 }
1215 if (theta > MAX_SWIPE_ANGLE) {
1216 // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
1217 return;
1218 } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
1219 // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
1220 // increase the touch slop to make it harder to begin scrolling the workspace. This
1221 // results in vertically scrolling widgets to more easily. The higher the angle, the
1222 // more we increase touch slop.
1223 theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
1224 float extraRatio = ((float) (Math.sqrt(theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_AN🔵
1225 super.determineScrollingStart(ev, 1 + (TOUCH_SLOP_DAMPING_FACTOR * extraRatio));
1226 } else {
1227 // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
1228 super.determineScrollingStart(ev);
1229 }
1230 }
1231
1232 protected void onPageBeginMoving() {
1233 super.onPageBeginMoving();
1234 if (isHardwareAccelerated()) {
1235 updateChildrenLayersEnabled(false);
1236 } else if (mNextPage != INVALID_PAGE) {
1237 // we're snapping to a particular screen
1238 enableChildrenCache(mCurrentPage, mNextPage);
1239 } else {
1240 // this is when user is actively dragging a particular screen, they might
1241 // swipe it either left or right (but we won't advance by more than one screen)
1242 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
1243 }
1244 }
1245
1246 protected void onPageEndMoving() {
1247 super.onPageEndMoving();
1248 if (isHardwareAccelerated()) {
1249 updateChildrenLayersEnabled(false);
1250 } else {
1251 clearChildrenCache();
1252 }
1253 if (mDragController.isDragging()) {
1254 if (workspaceInModalState()) {
1255 // If we are in springloaded mode, then force an event to check if the current touch
1256 // is under a new page (to scroll to)
1257 mDragController.forceTouchMove();
1258 }
1259 }
1260 if (mDelayedResizeRunnable != null) {
1261 mDelayedResizeRunnable.run();
1262 mDelayedResizeRunnable = null;
1263 }
1264 if (mDelayedSnapToPageRunnable != null) {
1265 mDelayedSnapToPageRunnable.run();
1266 mDelayedSnapToPageRunnable = null;
1267 }
1268 if (mStripScreensOnPageStopMoving) {
1269 stripEmptyScreens();
1270 mStripScreensOnPageStopMoving = false;
1271 }
1272 }
1273
1274 @Override
1275 protected void notifyPageSwitchListener() {
1276 super.notifyPageSwitchListener();
1277 Launcher.setScreen(getNextPage());
1278 if ((hasCustomContent() && (getNextPage() == 0)) && (!mCustomContentShowing)) {
1279 mCustomContentShowing = true;
1280 if (mCustomContentCallbacks != null) {
1281 mCustomContentCallbacks.onShow(false);
1282 mCustomContentShowTime = System.currentTimeMillis();
1283 mLauncher.updateVoiceButtonProxyVisible(false);
1284 }
1285 } else if ((hasCustomContent() && (getNextPage() != 0)) && mCustomContentShowing) {
1286 mCustomContentShowing = false;
1287 if (mCustomContentCallbacks != null) {
1288 mCustomContentCallbacks.onHide();
1289 mLauncher.resetQSBScroll();
1290 mLauncher.updateVoiceButtonProxyVisible(false);
1291 }
1292 }
1293 }
1294
1295 protected CustomContentCallbacks getCustomContentCallbacks() {
1296 return mCustomContentCallbacks;
1297 }
1298
1299 protected void setWallpaperDimension() {
1300 new AsyncTask<Void, Void, Void>() {
1301 public Void doInBackground(Void... args) {
1302 String spKey = WallpaperCropActivity.getSharedPreferencesKey();
1303 SharedPreferences sp = mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
1304 LauncherWallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(), sp, m🔵
1305 return null;
1306 }
1307 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ((Void) (null)));
1308 }
1309
1310 protected void snapToPage(int whichPage, Runnable r) {
1311 snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r);
1312 }
1313
1314 protected void snapToPage(int whichPage, int duration, Runnable r) {
1315 if (mDelayedSnapToPageRunnable != null) {
1316 mDelayedSnapToPageRunnable.run();
1317 }
1318 mDelayedSnapToPageRunnable = r;
1319 snapToPage(whichPage, duration);
1320 }
1321
1322 public void snapToScreenId(long screenId) {
1323 snapToScreenId(screenId, null);
1324 }
1325
1326 protected void snapToScreenId(long screenId, Runnable r) {
1327 snapToPage(getPageIndexForScreenId(screenId), r);
1328 }
1329
1330 class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
1331 float mFinalOffset = 0.0F;
1332
1333 float mCurrentOffset = 0.5F;// to force an initial update
1334
1335
1336 boolean mWaitingForUpdate;
1337
1338 Choreographer mChoreographer;
1339
1340 Interpolator mInterpolator;
1341
1342 boolean mAnimating;
1343
1344 long mAnimationStartTime;
1345
1346 float mAnimationStartOffset;
1347
1348 private final int ANIMATION_DURATION = 250;
1349
1350 // Don't use all the wallpaper for parallax until you have at least this many pages
1351 private final int MIN_PARALLAX_PAGE_SPAN = 3;
1352
1353 int mNumScreens;
1354
1355 public WallpaperOffsetInterpolator() {
1356 mChoreographer = Choreographer.getInstance();
1357 mInterpolator = new DecelerateInterpolator(1.5F);
1358 }
1359
1360 @Override
1361 public void doFrame(long frameTimeNanos) {
1362 updateOffset(false);
1363 }
1364
1365 private void updateOffset(boolean force) {
1366 if (mWaitingForUpdate || force) {
1367 mWaitingForUpdate = false;
1368 if (computeScrollOffset() && (mWindowToken != null)) {
1369 try {
1370 mWallpaperManager.setWallpaperOffsets(mWindowToken, mWallpaperOffset.getCurrX(), 🔵
1371 setWallpaperOffsetSteps();
1372 } catch (java.lang.IllegalArgumentException e) {
1373 Log.e(TAG, "Error updating wallpaper offset: " + e);
1374 }
1375 }
1376 }
1377 }
1378
1379 public boolean computeScrollOffset() {
1380 final float oldOffset = mCurrentOffset;
1381 if (mAnimating) {
1382 long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
1383 float t0 = durationSinceAnimation / ((float) (ANIMATION_DURATION));
1384 float t1 = mInterpolator.getInterpolation(t0);
1385 mCurrentOffset = mAnimationStartOffset + ((mFinalOffset - mAnimationStartOffset) * t1);
1386 mAnimating = durationSinceAnimation < ANIMATION_DURATION;
1387 } else {
1388 mCurrentOffset = mFinalOffset;
1389 }
1390 if (Math.abs(mCurrentOffset - mFinalOffset) > 1.0E-7F) {
1391 scheduleUpdate();
1392 }
1393 if (Math.abs(oldOffset - mCurrentOffset) > 1.0E-7F) {
1394 return true;
1395 }
1396 return false;
1397 }
1398
1399 private float wallpaperOffsetForCurrentScroll() {
1400 if (getChildCount() <= 1) {
1401 return 0;
1402 }
1403 // Exclude the leftmost page
1404 int emptyExtraPages = numEmptyScreensToIgnore();
1405 int firstIndex = numCustomPages();
1406 // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
1407 int lastIndex = (getChildCount() - 1) - emptyExtraPages;
1408 if (isLayoutRtl()) {
1409 int temp = firstIndex;
1410 firstIndex = lastIndex;
1411 lastIndex = temp;
1412 }
1413 int firstPageScrollX = getScrollForPage(firstIndex);
1414 int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX;
1415 if (scrollRange == 0) {
1416 return 0;
1417 } else {
1418 // TODO: do different behavior if it's a live wallpaper?
1419 // Sometimes the left parameter of the pages is animated during a layout transition;
1420 // this parameter offsets it to keep the wallpaper from animating as well
1421 int adjustedScroll = (getScrollX() - firstPageScrollX) - getLayoutTransitionOffsetForPage🔵
1422 float offset = Math.min(1, adjustedScroll / ((float) (scrollRange)));
1423 offset = Math.max(0, offset);
1424 // Don't use up all the wallpaper parallax until you have at least
1425 // MIN_PARALLAX_PAGE_SPAN pages
1426 int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
1427 int parallaxPageSpan;
1428 if (mWallpaperIsLiveWallpaper) {
1429 parallaxPageSpan = numScrollingPages - 1;
1430 } else {
1431 parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
1432 }
1433 mNumPagesForWallpaperParallax = parallaxPageSpan;
1434 // On RTL devices, push the wallpaper offset to the right if we don't have enough
1435 // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
1436 int padding = (isLayoutRtl()) ? (parallaxPageSpan - numScrollingPages) + 1 : 0;
1437 return (offset * ((padding + numScrollingPages) - 1)) / parallaxPageSpan;
1438 }
1439 }
1440
1441 private int numEmptyScreensToIgnore() {
1442 int numScrollingPages = getChildCount() - numCustomPages();
1443 if ((numScrollingPages >= MIN_PARALLAX_PAGE_SPAN) && hasExtraEmptyScreen()) {
1444 return 1;
1445 } else {
1446 return 0;
1447 }
1448 }
1449
1450 private int getNumScreensExcludingEmptyAndCustom() {
1451 int numScrollingPages = (getChildCount() - numEmptyScreensToIgnore()) - numCustomPages();
1452 return numScrollingPages;
1453 }
1454
1455 public void syncWithScroll() {
1456 float offset = wallpaperOffsetForCurrentScroll();
1457 mWallpaperOffset.setFinalX(offset);
1458 updateOffset(true);
1459 }
1460
1461 public float getCurrX() {
1462 return mCurrentOffset;
1463 }
1464
1465 public float getFinalX() {
1466 return mFinalOffset;
1467 }
1468
1469 private void animateToFinal() {
1470 mAnimating = true;
1471 mAnimationStartOffset = mCurrentOffset;
1472 mAnimationStartTime = System.currentTimeMillis();
1473 }
1474
1475 private void setWallpaperOffsetSteps() {
1476 // Set wallpaper offset steps (1 / (number of screens - 1))
1477 float xOffset = 1.0F / mNumPagesForWallpaperParallax;
1478 if (xOffset != mLastSetWallpaperOffsetSteps) {
1479 mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0F);
1480 mLastSetWallpaperOffsetSteps = xOffset;
1481 }
1482 }
1483
1484 public void setFinalX(float x) {
1485 scheduleUpdate();
1486 mFinalOffset = Math.max(0.0F, Math.min(x, 1.0F));
1487 if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
1488 if (mNumScreens > 0) {
1489 // Don't animate if we're going from 0 screens
1490 animateToFinal();
1491 }
1492 mNumScreens = getNumScreensExcludingEmptyAndCustom();
1493 }
1494 }
1495
1496 private void scheduleUpdate() {
1497 if (!mWaitingForUpdate) {
1498 mChoreographer.postFrameCallback(this);
1499 mWaitingForUpdate = true;
1500 }
1501 }
1502
1503 public void jumpToFinal() {
1504 mCurrentOffset = mFinalOffset;
1505 }
1506 }
1507
1508 @Override
1509 public void computeScroll() {
1510 super.computeScroll();
1511 mWallpaperOffset.syncWithScroll();
1512 }
1513
1514 @Override
1515 public void announceForAccessibility(CharSequence text) {
1516 // Don't announce if apps is on top of us.
1517 if (!mLauncher.isAllAppsVisible()) {
1518 super.announceForAccessibility(text);
1519 }
1520 }
1521
1522 void showOutlines() {
1523 if ((!workspaceInModalState()) && (!mIsSwitchingState)) {
1524 if (mChildrenOutlineFadeOutAnimation != null) {
1525 mChildrenOutlineFadeOutAnimation.cancel();
1526 }
1527 if (mChildrenOutlineFadeInAnimation != null) {
1528 mChildrenOutlineFadeInAnimation.cancel();
1529 }
1530 mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0🔵
1531 mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
1532 mChildrenOutlineFadeInAnimation.start();
1533 }
1534 }
1535
1536 void hideOutlines() {
1537 if ((!workspaceInModalState()) && (!mIsSwitchingState)) {
1538 if (mChildrenOutlineFadeInAnimation != null) {
1539 mChildrenOutlineFadeInAnimation.cancel();
1540 }
1541 if (mChildrenOutlineFadeOutAnimation != null) {
1542 mChildrenOutlineFadeOutAnimation.cancel();
1543 }
1544 mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.🔵
1545 mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
1546 mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
1547 mChildrenOutlineFadeOutAnimation.start();
1548 }
1549 }
1550
1551 public void showOutlinesTemporarily() {
1552 if (!mIsPageMoving && !isTouchActive()) {
1553 snapToPage(mCurrentPage);
1554 }
1555 }
1556
1557 public void setChildrenOutlineAlpha(float alpha) {
1558 mChildrenOutlineAlpha = alpha;
1559 for (int i = 0; i < getChildCount(); i++) {
1560 CellLayout cl = (CellLayout) getChildAt(i);
1561 cl.setBackgroundAlpha(alpha);
1562 }
1563 }
1564
1565 public float getChildrenOutlineAlpha() {
1566 return mChildrenOutlineAlpha;
1567 }
1568
1569 private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1570 final DragLayer dragLayer = mLauncher.getDragLayer();
1571 if (mBackgroundFadeInAnimation != null) {
1572 mBackgroundFadeInAnimation.cancel();
1573 mBackgroundFadeInAnimation = null;
1574 }
1575 if (mBackgroundFadeOutAnimation != null) {
1576 mBackgroundFadeOutAnimation.cancel();
1577 mBackgroundFadeOutAnimation = null;
1578 }
1579 float startAlpha = dragLayer.getBackgroundAlpha();
1580 if (finalAlpha != startAlpha) {
1581 if (animated) {
1582 mBackgroundFadeOutAnimation = LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
1583 mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1584 public void onAnimationUpdate(ValueAnimator animation) {
1585 dragLayer.setBackgroundAlpha(((Float) (animation.getAnimatedValue())).floatValue(🔵
1586 }
1587 });
1588 mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5F));
1589 mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1590 mBackgroundFadeOutAnimation.start();
1591 } else {
1592 dragLayer.setBackgroundAlpha(finalAlpha);
1593 }
1594 }
1595 }
1596
1597 float backgroundAlphaInterpolator(float r) {
1598 float pivotA = 0.1f;
1599 float pivotB = 0.4f;
1600 if (r < pivotA) {
1601 return 0;
1602 } else if (r > pivotB) {
1603 return 1.0f;
1604 } else {
1605 return (r - pivotA)/(pivotB - pivotA);
1606 }
1607 }
1608
1609 private void updatePageAlphaValues(int screenCenter) {
1610 boolean isInOverscroll = (mOverScrollX < 0) || (mOverScrollX > mMaxScrollX);
1611 if (((mWorkspaceFadeInAdjacentScreens && (!workspaceInModalState())) && (!mIsSwitchingState)) && 🔵
1612 for (int i = numCustomPages(); i < getChildCount(); i++) {
1613 CellLayout child = ((CellLayout) (getChildAt(i)));
1614 if (child != null) {
1615 float scrollProgress = getScrollProgress(screenCenter, child, i);
1616 float alpha = 1 - Math.abs(scrollProgress);
1617 child.getShortcutsAndWidgets().setAlpha(alpha);
1618 //child.setBackgroundAlphaMultiplier(1 - alpha);
1619 }
1620 }
1621 }
1622 }
1623
1624 private void setChildrenBackgroundAlphaMultipliers(float a) {
1625 for (int i = 0; i < getChildCount(); i++) {
1626 CellLayout child = (CellLayout) getChildAt(i);
1627 child.setBackgroundAlphaMultiplier(a);
1628 }
1629 }
1630
1631 public boolean hasCustomContent() {
1632 return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
1633 }
1634
1635 public int numCustomPages() {
1636 return hasCustomContent() ? 1 : 0;
1637 }
1638
1639 public boolean isOnOrMovingToCustomContent() {
1640 return hasCustomContent() && getNextPage() == 0;
1641 }
1642
1643 private void updateStateForCustomContent(int screenCenter) {
1644 float translationX = 0;
1645 float progress = 0;
1646 if (hasCustomContent()) {
1647 int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
1648 int scrollDelta = (getScrollX() - getScrollForPage(index)) - getLayoutTransitionOffsetForPage🔵
1649 float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
1650 translationX = scrollRange - scrollDelta;
1651 progress = (scrollRange - scrollDelta) / scrollRange;
1652 if (isLayoutRtl()) {
1653 translationX = Math.min(0, translationX);
1654 } else {
1655 translationX = Math.max(0, translationX);
1656 }
1657 progress = Math.max(0, progress);
1658 }
1659 if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) {
1660 return;
1661 }
1662 CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1663 if (((progress > 0) && (cc.getVisibility() != VISIBLE)) && (!workspaceInModalState())) {
1664 cc.setVisibility(VISIBLE);
1665 }
1666 mLastCustomContentScrollProgress = progress;
1667 mLauncher.getDragLayer().setBackgroundAlpha(progress * 0.8F);
1668 if (mLauncher.getHotseat() != null) {
1669 mLauncher.getHotseat().setTranslationX(translationX);
1670 }
1671 if (getPageIndicator() != null) {
1672 getPageIndicator().setTranslationX(translationX);
1673 }
1674 if (mCustomContentCallbacks != null) {
1675 mCustomContentCallbacks.onScrollProgressChanged(progress);
1676 }
1677 }
1678
1679 @Override
1680 protected OnClickListener getPageIndicatorClickListener() {
1681 AccessibilityManager am = (AccessibilityManager)
1682 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1683 if (!am.isTouchExplorationEnabled()) {
1684 return null;
1685 }
1686 OnClickListener listener = new OnClickListener() {
1687 @Override
1688 public void onClick(View arg0) {
1689 enterOverviewMode();
1690 }
1691 };
1692 return listener;
1693 }
1694
1695 @Override
1696 protected void screenScrolled(int screenCenter) {
1697 final boolean isRtl = isLayoutRtl();
1698 super.screenScrolled(screenCenter);
1699 updatePageAlphaValues(screenCenter);
1700 updateStateForCustomContent(screenCenter);
1701 enableHwLayersOnVisiblePages();
1702 boolean shouldOverScroll = (mOverScrollX < 0) || (mOverScrollX > mMaxScrollX);
1703 if (shouldOverScroll) {
1704 int index = 0;
1705 final int lowerIndex = 0;
1706 final int upperIndex = getChildCount() - 1;
1707 final boolean isLeftPage = mOverScrollX < 0;
1708 index = (((!isRtl) && isLeftPage) || (isRtl && (!isLeftPage))) ? lowerIndex : upperIndex;
1709 CellLayout cl = ((CellLayout) (getChildAt(index)));
1710 float effect = Math.abs(mOverScrollEffect);
1711 cl.setOverScrollAmount(Math.abs(effect), isLeftPage);
1712 mOverscrollEffectSet = true;
1713 } else if (mOverscrollEffectSet && (getChildCount() > 0)) {
1714 mOverscrollEffectSet = false;
1715 ((CellLayout) (getChildAt(0))).setOverScrollAmount(0, false);
1716 ((CellLayout) (getChildAt(getChildCount() - 1))).setOverScrollAmount(0, false);
1717 }
1718 }
1719
1720 @Override
1721 protected void overScroll(float amount) {
1722 boolean shouldOverScroll = ((amount < 0) && ((!hasCustomContent()) || isLayoutRtl())) || ((amount🔵
1723 if (shouldOverScroll) {
1724 dampedOverScroll(amount);
1725 mOverScrollEffect = acceleratedOverFactor(amount);
1726 } else {
1727 mOverScrollEffect = 0;
1728 }
1729 }
1730
1731 protected void onAttachedToWindow() {
1732 super.onAttachedToWindow();
1733 mWindowToken = getWindowToken();
1734 computeScroll();
1735 mDragController.setWindowToken(mWindowToken);
1736 }
1737
1738 protected void onDetachedFromWindow() {
1739 super.onDetachedFromWindow();
1740 mWindowToken = null;
1741 }
1742
1743 protected void onResume() {
1744 if (getPageIndicator() != null) {
1745 // In case accessibility state has changed, we need to perform this on every
1746 // attach to window
1747 OnClickListener listener = getPageIndicatorClickListener();
1748 if (listener != null) {
1749 getPageIndicator().setOnClickListener(listener);
1750 }
1751 }
1752 AccessibilityManager am = (AccessibilityManager)
1753 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1754 sAccessibilityEnabled = am.isEnabled();
1755
1756 // Update wallpaper dimensions if they were changed since last onResume
1757 // (we also always set the wallpaper dimensions in the constructor)
1758 if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
1759 setWallpaperDimension();
1760 }
1761 mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null;
1762 // Force the wallpaper offset steps to be set again, because another app might have changed
1763 // them
1764 mLastSetWallpaperOffsetSteps = 0f;
1765 }
1766
1767 @Override
1768 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1769 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1770 mWallpaperOffset.syncWithScroll();
1771 mWallpaperOffset.jumpToFinal();
1772 }
1773 super.onLayout(changed, left, top, right, bottom);
1774 }
1775
1776 @Override
1777 protected void onDraw(Canvas canvas) {
1778 super.onDraw(canvas);
1779 // Call back to LauncherModel to finish binding after the first draw
1780 post(mBindPages);
1781 }
1782
1783 @Override
1784 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1785 if (!mLauncher.isAllAppsVisible()) {
1786 final Folder openFolder = getOpenFolder();
1787 if (openFolder != null) {
1788 return openFolder.requestFocus(direction, previouslyFocusedRect);
1789 } else {
1790 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1791 }
1792 }
1793 return false;
1794 }
1795
1796 @Override
1797 public int getDescendantFocusability() {
1798 if (workspaceInModalState()) {
1799 return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1800 }
1801 return super.getDescendantFocusability();
1802 }
1803
1804 @Override
1805 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1806 if (!mLauncher.isAllAppsVisible()) {
1807 final Folder openFolder = getOpenFolder();
1808 if (openFolder != null) {
1809 openFolder.addFocusables(views, direction);
1810 } else {
1811 super.addFocusables(views, direction, focusableMode);
1812 }
1813 }
1814 }
1815
1816 public boolean workspaceInModalState() {
1817 return mState != State.NORMAL;
1818 }
1819
1820 void enableChildrenCache(int fromPage, int toPage) {
1821 if (fromPage > toPage) {
1822 final int temp = fromPage;
1823 fromPage = toPage;
1824 toPage = temp;
1825 }
1826
1827 final int screenCount = getChildCount();
1828
1829 fromPage = Math.max(fromPage, 0);
1830 toPage = Math.min(toPage, screenCount - 1);
1831
1832 for (int i = fromPage; i <= toPage; i++) {
1833 final CellLayout layout = (CellLayout) getChildAt(i);
1834 layout.setChildrenDrawnWithCacheEnabled(true);
1835 layout.setChildrenDrawingCacheEnabled(true);
1836 }
1837 }
1838
1839 void clearChildrenCache() {
1840 final int screenCount = getChildCount();
1841 for (int i = 0; i < screenCount; i++) {
1842 final CellLayout layout = (CellLayout) getChildAt(i);
1843 layout.setChildrenDrawnWithCacheEnabled(false);
1844 // In software mode, we don't want the items to continue to be drawn into bitmaps
1845 if (!isHardwareAccelerated()) {
1846 layout.setChildrenDrawingCacheEnabled(false);
1847 }
1848 }
1849 }
1850
1851 private void updateChildrenLayersEnabled(boolean force) {
1852 boolean small = (mState == State.OVERVIEW) || mIsSwitchingState;
1853 boolean enableChildrenLayers = ((force || small) || mAnimatingViewIntoPlace) || isPageMoving();
1854 if (enableChildrenLayers != mChildrenLayersEnabled) {
1855 mChildrenLayersEnabled = enableChildrenLayers;
1856 if (mChildrenLayersEnabled) {
1857 enableHwLayersOnVisiblePages();
1858 } else {
1859 for (int i = 0; i < getPageCount(); i++) {
1860 final CellLayout cl = ((CellLayout) (getChildAt(i)));
1861 cl.enableHardwareLayer(false);
1862 }
1863 }
1864 }
1865 }
1866
1867 private void enableHwLayersOnVisiblePages() {
1868 if (mChildrenLayersEnabled) {
1869 final int screenCount = getChildCount();
1870 getVisiblePages(mTempVisiblePagesRange);
1871 int leftScreen = mTempVisiblePagesRange[0];
1872 int rightScreen = mTempVisiblePagesRange[1];
1873 if (leftScreen == rightScreen) {
1874 // make sure we're caching at least two pages always
1875 if (rightScreen < screenCount - 1) {
1876 rightScreen++;
1877 } else if (leftScreen > 0) {
1878 leftScreen--;
1879 }
1880 }
1881
1882 final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1883 for (int i = 0; i < screenCount; i++) {
1884 final CellLayout layout = (CellLayout) getPageAt(i);
1885
1886 // enable layers between left and right screen inclusive, except for the
1887 // customScreen, which may animate its content during transitions.
1888 boolean enableLayer = layout != customScreen &&
1889 leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
1890 layout.enableHardwareLayer(enableLayer);
1891 }
1892 }
1893 }
1894
1895 public void buildPageHardwareLayers() {
1896 // force layers to be enabled just for the call to buildLayer
1897 updateChildrenLayersEnabled(true);
1898 if (getWindowToken() != null) {
1899 final int childCount = getChildCount();
1900 for (int i = 0; i < childCount; i++) {
1901 CellLayout cl = (CellLayout) getChildAt(i);
1902 cl.buildHardwareLayer();
1903 }
1904 }
1905 updateChildrenLayersEnabled(false);
1906 }
1907
1908 protected void onWallpaperTap(MotionEvent ev) {
1909 final int[] position = mTempCell;
1910 getLocationOnScreen(position);
1911
1912 int pointerIndex = ev.getActionIndex();
1913 position[0] += (int) ev.getX(pointerIndex);
1914 position[1] += (int) ev.getY(pointerIndex);
1915
1916 mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1917 ev.getAction() == MotionEvent.ACTION_UP
1918 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1919 position[0], position[1], 0, null);
1920 }
1921
1922 /* This interpolator emulates the rate at which the perceived scale of an object changes
1923 as its distance from a camera increases. When this interpolator is applied to a scale
1924 animation on a view, it evokes the sense that the object is shrinking due to moving away
1925 from the camera.
1926 */
1927 static class ZInterpolator implements TimeInterpolator {
1928 private float focalLength;
1929
1930 public ZInterpolator(float foc) {
1931 focalLength = foc;
1932 }
1933
1934 public float getInterpolation(float input) {
1935 return (1.0F - (focalLength / (focalLength + input))) / (1.0F - (focalLength / (focalLength +🔵
1936 }
1937 }
1938
1939 /* The exact reverse of ZInterpolator. */
1940 static class InverseZInterpolator implements TimeInterpolator {
1941 private ZInterpolator zInterpolator;
1942
1943 public InverseZInterpolator(float foc) {
1944 zInterpolator = new ZInterpolator(foc);
1945 }
1946
1947 public float getInterpolation(float input) {
1948 return 1 - zInterpolator.getInterpolation(1 - input);
1949 }
1950 }
1951
1952 /* ZInterpolator compounded with an ease-out. */
1953 static class ZoomOutInterpolator implements TimeInterpolator {
1954 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75F);
1955
1956 private final ZInterpolator zInterpolator = new ZInterpolator(0.13F);
1957
1958 public float getInterpolation(float input) {
1959 return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1960 }
1961 }
1962
1963 /* InvereZInterpolator compounded with an ease-out. */
1964 static class ZoomInInterpolator implements TimeInterpolator {
1965 private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35F);
1966
1967 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0F);
1968
1969 public float getInterpolation(float input) {
1970 return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1971 }
1972 }
1973
1974 private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1975
1976 /*
1977 *
1978 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1979 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1980 *
1981 * These methods mark the appropriate pages as accepting drops (which alters their visual
1982 * appearance).
1983 *
1984 */
1985 private static Rect getDrawableBounds(Drawable d) {
1986 Rect bounds = new Rect();
1987 d.copyBounds(bounds);
1988 if ((bounds.width() == 0) || (bounds.height() == 0)) {
1989 bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
1990 } else {
1991 bounds.offsetTo(0, 0);
1992 }
1993 if (d instanceof PreloadIconDrawable) {
1994 int inset = -((PreloadIconDrawable) (d)).getOutset();
1995 bounds.inset(inset, inset);
1996 }
1997 return bounds;
1998 }
1999
2000 public void onExternalDragStartedWithItem(View v) {
2001 // Compose a drag bitmap with the view scaled to the icon size
2002 LauncherAppState app = LauncherAppState.getInstance();
2003 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2004 int iconSize = grid.iconSizePx;
2005 int bmpWidth = v.getMeasuredWidth();
2006 int bmpHeight = v.getMeasuredHeight();
2007 // If this is a text view, use its drawable instead
2008 if (v instanceof TextView) {
2009 TextView tv = ((TextView) (v));
2010 Drawable d = tv.getCompoundDrawables()[1];
2011 Rect bounds = getDrawableBounds(d);
2012 bmpWidth = bounds.width();
2013 bmpHeight = bounds.height();
2014 }
2015 // Compose the bitmap to create the icon from
2016 Bitmap b = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
2017 mCanvas.setBitmap(b);
2018 drawDragView(v, mCanvas, 0);
2019 mCanvas.setBitmap(null);
2020 // The outline is used to visualize where the item will land if dropped
2021 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, iconSize, iconSize, true);
2022 }
2023
2024 public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
2025 int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
2026 // The outline is used to visualize where the item will land if dropped
2027 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
2028 }
2029
2030 public void exitWidgetResizeMode() {
2031 DragLayer dragLayer = mLauncher.getDragLayer();
2032 dragLayer.clearAllResizeFrames();
2033 }
2034
2035 private void initAnimationArrays() {
2036 final int childCount = getChildCount();
2037 if (mLastChildCount == childCount) return;
2038
2039 mOldBackgroundAlphas = new float[childCount];
2040 mOldAlphas = new float[childCount];
2041 mNewBackgroundAlphas = new float[childCount];
2042 mNewAlphas = new float[childCount];
2043 }
2044
2045 Animator getChangeStateAnimation(final State state, boolean animated, ArrayList<View> layerViews) {
2046 return getChangeStateAnimation(state, animated, 0, -1, layerViews);
2047 }
2048
2049 @Override
2050 protected void getFreeScrollPageRange(int[] range) {
2051 getOverviewModePages(range);
2052 }
2053
2054 private void getOverviewModePages(int[] range) {
2055 int start = numCustomPages();
2056 int end = getChildCount() - 1;
2057 range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
2058 range[1] = Math.max(0, end);
2059 }
2060
2061 protected void onStartReordering() {
2062 super.onStartReordering();
2063 showOutlines();
2064 // Reordering handles its own animations, disable the automatic ones.
2065 disableLayoutTransitions();
2066 }
2067
2068 protected void onEndReordering() {
2069 super.onEndReordering();
2070 if (mLauncher.isWorkspaceLoading()) {
2071 // Invalid and dangerous operation if workspace is loading
2072 return;
2073 }
2074 hideOutlines();
2075 mScreenOrder.clear();
2076 int count = getChildCount();
2077 for (int i = 0; i < count; i++) {
2078 CellLayout cl = ((CellLayout) (getChildAt(i)));
2079 mScreenOrder.add(getIdForScreen(cl));
2080 }
2081 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
2082 // Re-enable auto layout transitions for page deletion.
2083 enableLayoutTransitions();
2084 }
2085
2086 public boolean isInOverviewMode() {
2087 return mState == State.OVERVIEW;
2088 }
2089
2090 public boolean enterOverviewMode() {
2091 if (mTouchState != TOUCH_STATE_REST) {
2092 return false;
2093 }
2094 enableOverviewMode(true, -1, true);
2095 return true;
2096 }
2097
2098 public void exitOverviewMode(boolean animated) {
2099 exitOverviewMode(-1, animated);
2100 }
2101
2102 public void exitOverviewMode(int snapPage, boolean animated) {
2103 enableOverviewMode(false, snapPage, animated);
2104 }
2105
2106 private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
2107 State finalState = Workspace.State.OVERVIEW;
2108 if (!enable) {
2109 finalState = Workspace.State.NORMAL;
2110 }
2111
2112 Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
2113 if (workspaceAnim != null) {
2114 onTransitionPrepare();
2115 workspaceAnim.addListener(new AnimatorListenerAdapter() {
2116 @Override
2117 public void onAnimationEnd(Animator arg0) {
2118 onTransitionEnd();
2119 }
2120 });
2121 workspaceAnim.start();
2122 }
2123 }
2124
2125 int getOverviewModeTranslationY() {
2126 LauncherAppState app = LauncherAppState.getInstance();
2127 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2128 Rect overviewBar = grid.getOverviewModeButtonBarRect();
2129
2130 int availableHeight = getViewportHeight();
2131 int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
2132 int offsetFromTopEdge = (availableHeight - scaledHeight) / 2;
2133 int offsetToCenterInOverview = (availableHeight - mInsets.top - overviewBar.height()
2134 - scaledHeight) / 2;
2135
2136 return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
2137 }
2138
2139 boolean shouldVoiceButtonProxyBeVisible() {
2140 if (isOnOrMovingToCustomContent()) {
2141 return false;
2142 }
2143 if (mState != State.NORMAL) {
2144 return false;
2145 }
2146 return true;
2147 }
2148
2149 public void updateInteractionForState() {
2150 if (mState != State.NORMAL) {
2151 mLauncher.onInteractionBegin();
2152 } else {
2153 mLauncher.onInteractionEnd();
2154 }
2155 }
2156
2157 private void setState(State state) {
2158 mState = state;
2159 updateInteractionForState();
2160 updateAccessibilityFlags();
2161 }
2162
2163 State getState() {
2164 return mState;
2165 }
2166
2167 private void updateAccessibilityFlags() {
2168 int accessible = mState == State.NORMAL ?
2169 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
2170 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
2171 setImportantForAccessibility(accessible);
2172 }
2173
2174 private static final int HIDE_WORKSPACE_DURATION = 100;
2175
2176 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
2177 return getChangeStateAnimation(state, animated, delay, snapPage, null);
2178 }
2179
2180 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage, ArrayL🔵
2181 if (mState == state) {
2182 return null;
2183 }
2184 // Initialize animation arrays for the first time if necessary
2185 initAnimationArrays();
2186 AnimatorSet anim = (animated) ? LauncherAnimUtils.createAnimatorSet() : null;
2187 final State oldState = mState;
2188 final boolean oldStateIsNormal = oldState == State.NORMAL;
2189 final boolean oldStateIsSpringLoaded = oldState == State.SPRING_LOADED;
2190 final boolean oldStateIsNormalHidden = oldState == State.NORMAL_HIDDEN;
2191 final boolean oldStateIsOverviewHidden = oldState == State.OVERVIEW_HIDDEN;
2192 final boolean oldStateIsOverview = oldState == State.OVERVIEW;
2193 setState(state);
2194 final boolean stateIsNormal = state == State.NORMAL;
2195 final boolean stateIsSpringLoaded = state == State.SPRING_LOADED;
2196 final boolean stateIsNormalHidden = state == State.NORMAL_HIDDEN;
2197 final boolean stateIsOverviewHidden = state == State.OVERVIEW_HIDDEN;
2198 final boolean stateIsOverview = state == State.OVERVIEW;
2199 float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0F : 0.0F;
2200 float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1.0F : 0.0F;
2201 float finalOverviewPanelAlpha = (stateIsOverview) ? 1.0F : 0.0F;
2202 float finalSearchBarAlpha = (!stateIsNormal) ? 0.0F : 1.0F;
2203 float finalWorkspaceTranslationY = (stateIsOverview || stateIsOverviewHidden) ? getOverviewModeTr🔵
2204 boolean workspaceToAllApps = oldStateIsNormal && stateIsNormalHidden;
2205 boolean overviewToAllApps = oldStateIsOverview && stateIsOverviewHidden;
2206 boolean allAppsToWorkspace = stateIsNormalHidden && stateIsNormal;
2207 boolean workspaceToOverview = oldStateIsNormal && stateIsOverview;
2208 boolean overviewToWorkspace = oldStateIsOverview && stateIsNormal;
2209 mNewScale = 1.0F;
2210 if (oldStateIsOverview) {
2211 disableFreeScroll();
2212 } else if (stateIsOverview) {
2213 enableFreeScroll();
2214 }
2215 if (state != State.NORMAL) {
2216 if (stateIsSpringLoaded) {
2217 mNewScale = mSpringLoadedShrinkFactor;
2218 } else if (stateIsOverview || stateIsOverviewHidden) {
2219 mNewScale = mOverviewModeShrinkFactor;
2220 }
2221 }
2222 final int duration;
2223 if (workspaceToAllApps || overviewToAllApps) {
2224 //getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
2225 duration = HIDE_WORKSPACE_DURATION;
2226 } else if (workspaceToOverview || overviewToWorkspace) {
2227 duration = getResources().getInteger(R.integer.config_overviewTransitionTime);
2228 } else {
2229 duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
2230 }
2231 if (snapPage == (-1)) {
2232 snapPage = getPageNearestToCenterOfScreen();
2233 }
2234 snapToPage(snapPage, duration, mZoomInInterpolator);
2235 for (int i = 0; i < getChildCount(); i++) {
2236 final CellLayout cl = ((CellLayout) (getChildAt(i)));
2237 boolean isCurrentPage = i == snapPage;
2238 float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
2239 float finalAlpha;
2240 if (stateIsNormalHidden || stateIsOverviewHidden) {
2241 finalAlpha = 0.0F;
2242 } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
2243 finalAlpha = ((i == snapPage) || (i < numCustomPages())) ? 1.0F : 0.0F;
2244 } else {
2245 finalAlpha = 1.0F;
2246 }
2247 // If we are animating to/from the small state, then hide the side pages and fade the
2248 // current page in
2249 if (!mIsSwitchingState) {
2250 if (workspaceToAllApps || allAppsToWorkspace) {
2251 if (allAppsToWorkspace && isCurrentPage) {
2252 initialAlpha = 0.0F;
2253 } else if (!isCurrentPage) {
2254 initialAlpha = finalAlpha = 0.0F;
2255 }
2256 cl.setShortcutAndWidgetAlpha(initialAlpha);
2257 }
2258 }
2259 mOldAlphas[i] = initialAlpha;
2260 mNewAlphas[i] = finalAlpha;
2261 if (animated) {
2262 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
2263 mNewBackgroundAlphas[i] = finalBackgroundAlpha;
2264 } else {
2265 cl.setBackgroundAlpha(finalBackgroundAlpha);
2266 cl.setShortcutAndWidgetAlpha(finalAlpha);
2267 }
2268 }
2269 final View searchBar = mLauncher.getQsbBar();
2270 final View overviewPanel = mLauncher.getOverviewPanel();
2271 final View hotseat = mLauncher.getHotseat();
2272 final View pageIndicator = getPageIndicator();
2273 if (animated) {
2274 LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
2275 scale.scaleX(mNewScale).scaleY(mNewScale).translationY(finalWorkspaceTranslationY).setDuratio🔵
2276 anim.play(scale);
2277 for (int index = 0; index < getChildCount(); index++) {
2278 final int i = index;
2279 final CellLayout cl = ((CellLayout) (getChildAt(i)));
2280 float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
2281 if ((mOldAlphas[i] == 0) && (mNewAlphas[i] == 0)) {
2282 cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
2283 cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
2284 } else {
2285 if (layerViews != null) {
2286 layerViews.add(cl);
2287 }
2288 if ((mOldAlphas[i] != mNewAlphas[i]) || (currentAlpha != mNewAlphas[i])) {
2289 LauncherViewPropertyAnimator alphaAnim = new LauncherViewPropertyAnimator(cl.getS🔵
2290 alphaAnim.alpha(mNewAlphas[i]).setDuration(duration).setInterpolator(mZoomInInter🔵
2291 anim.play(alphaAnim);
2292 }
2293 if ((mOldBackgroundAlphas[i] != 0) || (mNewBackgroundAlphas[i] != 0)) {
2294 ValueAnimator bgAnim = LauncherAnimUtils.ofFloat(cl, 0.0F, 1.0F);
2295 bgAnim.setInterpolator(mZoomInInterpolator);
2296 bgAnim.setDuration(duration);
2297 bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2298 public void onAnimationUpdate(float a, float b) {
2299 cl.setBackgroundAlpha((a * mOldBackgroundAlphas[i]) + (b * mNewBackground🔵
2300 }
2301 });
2302 anim.play(bgAnim);
2303 }
2304 }
2305 }
2306 Animator pageIndicatorAlpha = null;
2307 if (pageIndicator != null) {
2308 pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator).alpha(finalHotseatAn🔵
2309 pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator));
2310 } else {
2311 // create a dummy animation so we don't need to do null checks later
2312 pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
2313 }
2314 Animator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat).alpha(finalHotseatAndPageIn🔵
2315 hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
2316 Animator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar).alpha(finalSearchBarAlp🔵
2317 searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
2318 Animator overviewPanelAlpha = new LauncherViewPropertyAnimator(overviewPanel).alpha(finalOver🔵
2319 overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
2320 // For animation optimations, we may need to provide the Launcher transition
2321 // with a set of views on which to force build layers in certain scenarios.
2322 hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2323 searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2324 overviewPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2325 if (layerViews != null) {
2326 layerViews.add(hotseat);
2327 layerViews.add(searchBar);
2328 layerViews.add(overviewPanel);
2329 }
2330 if (workspaceToOverview) {
2331 pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
2332 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
2333 overviewPanelAlpha.setInterpolator(null);
2334 } else if (overviewToWorkspace) {
2335 pageIndicatorAlpha.setInterpolator(null);
2336 hotseatAlpha.setInterpolator(null);
2337 overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
2338 }
2339 overviewPanelAlpha.setDuration(duration);
2340 pageIndicatorAlpha.setDuration(duration);
2341 hotseatAlpha.setDuration(duration);
2342 searchBarAlpha.setDuration(duration);
2343 anim.play(overviewPanelAlpha);
2344 anim.play(hotseatAlpha);
2345 anim.play(searchBarAlpha);
2346 anim.play(pageIndicatorAlpha);
2347 anim.setStartDelay(delay);
2348 } else {
2349 overviewPanel.setAlpha(finalOverviewPanelAlpha);
2350 AlphaUpdateListener.updateVisibility(overviewPanel);
2351 hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
2352 AlphaUpdateListener.updateVisibility(hotseat);
2353 if (pageIndicator != null) {
2354 pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
2355 AlphaUpdateListener.updateVisibility(pageIndicator);
2356 }
2357 searchBar.setAlpha(finalSearchBarAlpha);
2358 AlphaUpdateListener.updateVisibility(searchBar);
2359 updateCustomContentVisibility();
2360 setScaleX(mNewScale);
2361 setScaleY(mNewScale);
2362 setTranslationY(finalWorkspaceTranslationY);
2363 }
2364 mLauncher.updateVoiceButtonProxyVisible(false);
2365 if (stateIsNormal) {
2366 animateBackgroundGradient(0.0F, animated);
2367 } else {
2368 animateBackgroundGradient(getResources().getInteger(R.integer.config_workspaceScrimAlpha) / 1🔵
2369 }
2370 return anim;
2371 }
2372
2373 static class AlphaUpdateListener implements AnimatorUpdateListener , AnimatorListener {
2374 View view;
2375
2376 public AlphaUpdateListener(View v) {
2377 view = v;
2378 }
2379
2380 @Override
2381 public void onAnimationUpdate(ValueAnimator arg0) {
2382 updateVisibility(view);
2383 }
2384
2385 public static void updateVisibility(View view) {
2386 // We want to avoid the extra layout pass by setting the views to GONE unless
2387 // accessibility is on, in which case not setting them to GONE causes a glitch.
2388 int invisibleState = (sAccessibilityEnabled) ? GONE : INVISIBLE;
2389 if ((view.getAlpha() < ALPHA_CUTOFF_THRESHOLD) && (view.getVisibility() != invisibleState)) {
2390 view.setVisibility(invisibleState);
2391 } else if ((view.getAlpha() > ALPHA_CUTOFF_THRESHOLD) && (view.getVisibility() != VISIBLE)) {
2392 view.setVisibility(VISIBLE);
2393 }
2394 }
2395
2396 @Override
2397 public void onAnimationCancel(Animator arg0) {
2398 }
2399
2400 @Override
2401 public void onAnimationEnd(Animator arg0) {
2402 updateVisibility(view);
2403 }
2404
2405 @Override
2406 public void onAnimationRepeat(Animator arg0) {
2407 }
2408
2409 @Override
2410 public void onAnimationStart(Animator arg0) {
2411 // We want the views to be visible for animation, so fade-in/out is visible
2412 view.setVisibility(VISIBLE);
2413 }
2414 }
2415
2416 @Override
2417 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
2418 onTransitionPrepare();
2419 }
2420
2421 @Override
2422 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
2423 }
2424
2425 @Override
2426 public void onLauncherTransitionStep(Launcher l, float t) {
2427 mTransitionProgress = t;
2428 }
2429
2430 @Override
2431 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
2432 onTransitionEnd();
2433 }
2434
2435 private void onTransitionPrepare() {
2436 mIsSwitchingState = true;
2437
2438 // Invalidate here to ensure that the pages are rendered during the state change transition.
2439 invalidate();
2440
2441 updateChildrenLayersEnabled(false);
2442 hideCustomContentIfNecessary();
2443 }
2444
2445 void updateCustomContentVisibility() {
2446 int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
2447 if (hasCustomContent()) {
2448 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
2449 }
2450 }
2451
2452 void showCustomContentIfNecessary() {
2453 boolean show = mState == Workspace.State.NORMAL;
2454 if (show && hasCustomContent()) {
2455 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2456 }
2457 }
2458
2459 void hideCustomContentIfNecessary() {
2460 boolean hide = mState != Workspace.State.NORMAL;
2461 if (hide && hasCustomContent()) {
2462 disableLayoutTransitions();
2463 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
2464 enableLayoutTransitions();
2465 }
2466 }
2467
2468 private void onTransitionEnd() {
2469 mIsSwitchingState = false;
2470 updateChildrenLayersEnabled(false);
2471 showCustomContentIfNecessary();
2472 }
2473
2474 @Override
2475 public View getContent() {
2476 return this;
2477 }
2478
2479 /**
2480 * Draw the View v into the given Canvas.
2481 *
2482 * @param v the view to draw
2483 * @param destCanvas the canvas to draw on
2484 * @param padding the horizontal and vertical padding to use when drawing
2485 */
2486 private static void drawDragView(View v, Canvas destCanvas, int padding) {
2487 final Rect clipRect = sTempRect;
2488 v.getDrawingRect(clipRect);
2489 boolean textVisible = false;
2490 destCanvas.save();
2491 if (v instanceof TextView) {
2492 Drawable d = ((TextView) (v)).getCompoundDrawables()[1];
2493 Rect bounds = getDrawableBounds(d);
2494 clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding);
2495 destCanvas.translate((padding / 2) - bounds.left, (padding / 2) - bounds.top);
2496 d.draw(destCanvas);
2497 } else {
2498 if (v instanceof FolderIcon) {
2499 // For FolderIcons the text can bleed into the icon area, and so we need to
2500 // hide the text completely (which can't be achieved by clipping).
2501 if (((FolderIcon) (v)).getTextVisible()) {
2502 ((FolderIcon) (v)).setTextVisible(false);
2503 textVisible = true;
2504 }
2505 }
2506 destCanvas.translate((-v.getScrollX()) + (padding / 2), (-v.getScrollY()) + (padding / 2));
2507 destCanvas.clipRect(clipRect, Op.REPLACE);
2508 v.draw(destCanvas);
2509 // Restore text visibility of FolderIcon if necessary
2510 if (textVisible) {
2511 ((FolderIcon) (v)).setTextVisible(true);
2512 }
2513 }
2514 destCanvas.restore();
2515 }
2516
2517 /**
2518 * Returns a new bitmap to show when the given View is being dragged around.
2519 * Responsibility for the bitmap is transferred to the caller.
2520 *
2521 * @param expectedPadding
2522 * padding to add to the drag view. If a different padding was used
2523 * its value will be changed
2524 */
2525 public Bitmap createDragBitmap(View v, AtomicInteger expectedPadding) {
2526 Bitmap b;
2527 int padding = expectedPadding.get();
2528 if (v instanceof TextView) {
2529 Drawable d = ((TextView) (v)).getCompoundDrawables()[1];
2530 Rect bounds = getDrawableBounds(d);
2531 b = Bitmap.createBitmap(bounds.width() + padding, bounds.height() + padding, Bitmap.Config.AR🔵
2532 expectedPadding.set((padding - bounds.left) - bounds.top);
2533 } else {
2534 b = Bitmap.createBitmap(v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8🔵
2535 }
2536 mCanvas.setBitmap(b);
2537 drawDragView(v, mCanvas, padding);
2538 mCanvas.setBitmap(null);
2539 return b;
2540 }
2541
2542 /**
2543 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2544 * Responsibility for the bitmap is transferred to the caller.
2545 */
2546 private Bitmap createDragOutline(View v, int padding) {
2547 final int outlineColor = getResources().getColor(R.color.outline_color);
2548 final Bitmap b = Bitmap.createBitmap(v.getWidth() + padding, v.getHeight() + padding, Bitmap.Conf🔵
2549 mCanvas.setBitmap(b);
2550 drawDragView(v, mCanvas, padding);
2551 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor);
2552 mCanvas.setBitmap(null);
2553 return b;
2554 }
2555
2556 /**
2557 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2558 * Responsibility for the bitmap is transferred to the caller.
2559 */
2560 private Bitmap createDragOutline(Bitmap orig, int padding, int w, int h, boolean clipAlpha) {
2561 final int outlineColor = getResources().getColor(R.color.outline_color);
2562 final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
2563 mCanvas.setBitmap(b);
2564 Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
2565 float scaleFactor = Math.min((w - padding) / ((float) (orig.getWidth())), (h - padding) / ((float🔵
2566 int scaledWidth = ((int) (scaleFactor * orig.getWidth()));
2567 int scaledHeight = ((int) (scaleFactor * orig.getHeight()));
2568 Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2569 // center the image
2570 dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2571 mCanvas.drawBitmap(orig, src, dst, null);
2572 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor, clipAlpha);
2573 mCanvas.setBitmap(null);
2574 return b;
2575 }
2576
2577 void startDrag(CellLayout.CellInfo cellInfo) {
2578 View child = cellInfo.cell;
2579 // Make sure the drag was started by a long press as opposed to a long click.
2580 if (!child.isInTouchMode()) {
2581 return;
2582 }
2583 mDragInfo = cellInfo;
2584 child.setVisibility(INVISIBLE);
2585 CellLayout layout = ((CellLayout) (child.getParent().getParent()));
2586 layout.prepareChildForDrag(child);
2587 beginDragShared(child, this);
2588 }
2589
2590 public void beginDragShared(View child, DragSource source) {
2591 child.clearFocus();
2592 child.setPressed(false);
2593 // The outline is used to visualize where the item will land if dropped
2594 mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING);
2595 mLauncher.onDragStarted(child);
2596 // The drag bitmap follows the touch point around on the screen
2597 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2598 final Bitmap b = createDragBitmap(child, padding);
2599 final int bmpWidth = b.getWidth();
2600 final int bmpHeight = b.getHeight();
2601 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2602 int dragLayerX = Math.round(mTempXY[0] - ((bmpWidth - (scale * child.getWidth())) / 2));
2603 int dragLayerY = Math.round((mTempXY[1] - ((bmpHeight - (scale * bmpHeight)) / 2)) - (padding.get🔵
2604 LauncherAppState app = LauncherAppState.getInstance();
2605 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2606 Point dragVisualizeOffset = null;
2607 Rect dragRect = null;
2608 if (child instanceof BubbleTextView) {
2609 int iconSize = grid.iconSizePx;
2610 int top = child.getPaddingTop();
2611 int left = (bmpWidth - iconSize) / 2;
2612 int right = left + iconSize;
2613 int bottom = top + iconSize;
2614 dragLayerY += top;
2615 // Note: The drag region is used to calculate drag layer offsets, but the
2616 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2617 dragVisualizeOffset = new Point((-padding.get()) / 2, padding.get() / 2);
2618 dragRect = new Rect(left, top, right, bottom);
2619 } else if (child instanceof FolderIcon) {
2620 int previewSize = grid.folderIconSizePx;
2621 dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2622 }
2623 // Clear the pressed state if necessary
2624 if (child instanceof BubbleTextView) {
2625 BubbleTextView icon = ((BubbleTextView) (child));
2626 icon.clearPressedBackground();
2627 }
2628 if ((child.getTag() == null) || (!(child.getTag() instanceof ItemInfo))) {
2629 String msg = ((("Drag started with a view that has no tag set. This " + ("will cause a crash 🔵
2630 throw new IllegalStateException(msg);
2631 }
2632 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), DragCo🔵
2633 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2634 if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2635 mDragSourceInternal = ((ShortcutAndWidgetContainer) (child.getParent()));
2636 }
2637 b.recycle();
2638 }
2639
2640 public void beginExternalDragShared(View child, DragSource source) {
2641 LauncherAppState app = LauncherAppState.getInstance();
2642 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2643 int iconSize = grid.iconSizePx;
2644 // Notify launcher of drag start
2645 mLauncher.onDragStarted(child);
2646 // Compose a new drag bitmap that is of the icon size
2647 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2648 final Bitmap tmpB = createDragBitmap(child, padding);
2649 Bitmap b = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
2650 Paint p = new Paint();
2651 p.setFilterBitmap(true);
2652 mCanvas.setBitmap(b);
2653 mCanvas.drawBitmap(tmpB, new Rect(0, 0, tmpB.getWidth(), tmpB.getHeight()), new Rect(0, 0, iconSi🔵
2654 mCanvas.setBitmap(null);
2655 // Find the child's location on the screen
2656 int bmpWidth = tmpB.getWidth();
2657 float iconScale = ((float) (bmpWidth)) / iconSize;
2658 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY) * iconScale;
2659 int dragLayerX = Math.round(mTempXY[0] - ((bmpWidth - (scale * child.getWidth())) / 2));
2660 int dragLayerY = Math.round(mTempXY[1]);
2661 // Note: The drag region is used to calculate drag layer offsets, but the
2662 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2663 Point dragVisualizeOffset = new Point((-padding.get()) / 2, padding.get() / 2);
2664 Rect dragRect = new Rect(0, 0, iconSize, iconSize);
2665 if ((child.getTag() == null) || (!(child.getTag() instanceof ItemInfo))) {
2666 String msg = ((("Drag started with a view that has no tag set. This " + ("will cause a crash 🔵
2667 throw new IllegalStateException(msg);
2668 }
2669 // Start the drag
2670 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), DragCo🔵
2671 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2672 // Recycle temporary bitmaps
2673 tmpB.recycle();
2674 }
2675
2676 void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2677 int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2678 View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2679
2680 final int[] cellXY = new int[2];
2681 target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2682 addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2683
2684 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2685 cellXY[1]);
2686 }
2687
2688 public boolean transitionStateShouldAllowDrop() {
2689 return ((!isSwitchingState()) || (mTransitionProgress > 0.5F)) && ((mState == State.NORMAL) || (m🔵
2690 }
2691
2692 /**
2693 * {@inheritDoc}
2694 */
2695 public boolean acceptDrop(DragObject d) {
2696 // If it's an external drop (e.g. from All Apps), check if it should be accepted
2697 CellLayout dropTargetLayout = mDropToLayout;
2698 if (d.dragSource != this) {
2699 // Don't accept the drop if we're not over a screen at time of drop
2700 if (dropTargetLayout == null) {
2701 return false;
2702 }
2703 if (!transitionStateShouldAllowDrop()) {
2704 return false;
2705 }
2706 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, m🔵
2707 // We want the point to be mapped to the dragTarget.
2708 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2709 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2710 } else {
2711 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2712 }
2713 int spanX = 1;
2714 int spanY = 1;
2715 if (mDragInfo != null) {
2716 final CellLayout.CellInfo dragCellInfo = mDragInfo;
2717 spanX = dragCellInfo.spanX;
2718 spanY = dragCellInfo.spanY;
2719 } else {
2720 final ItemInfo dragInfo = ((ItemInfo) (d.dragInfo));
2721 spanX = dragInfo.spanX;
2722 spanY = dragInfo.spanY;
2723 }
2724 int minSpanX = spanX;
2725 int minSpanY = spanY;
2726 if (d.dragInfo instanceof PendingAddWidgetInfo) {
2727 minSpanX = ((PendingAddWidgetInfo) (d.dragInfo)).minSpanX;
2728 minSpanY = ((PendingAddWidgetInfo) (d.dragInfo)).minSpanY;
2729 }
2730 mTargetCell = findNearestArea(((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVisualCent🔵
2731 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisu🔵
2732 if (mCreateUserFolderOnDrop && willCreateUserFolder(((ItemInfo) (d.dragInfo)), dropTargetLayo🔵
2733 return true;
2734 }
2735 if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(((ItemInfo) (d.dragInfo)), drop🔵
2736 return true;
2737 }
2738 int[] resultSpan = new int[2];
2739 mTargetCell = dropTargetLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) (mDr🔵
2740 boolean foundCell = (mTargetCell[0] >= 0) && (mTargetCell[1] >= 0);
2741 // Don't accept the drop if there's no room for the item
2742 if (!foundCell) {
2743 // Don't show the message if we are dropping on the AllApps button and the hotseat
2744 // is full
2745 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2746 if ((mTargetCell != null) && isHotseat) {
2747 Hotseat hotseat = mLauncher.getHotseat();
2748 if (hotseat.isAllAppsButtonRank(hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell🔵
2749 return false;
2750 }
2751 }
2752 mLauncher.showOutOfSpaceMessage(isHotseat);
2753 return false;
2754 }
2755 }
2756 long screenId = getIdForScreen(dropTargetLayout);
2757 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2758 commitExtraEmptyScreen();
2759 }
2760 return true;
2761 }
2762
2763 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2764 distance, boolean considerTimeout) {
2765 if (distance > mMaxDistanceForFolderCreation) return false;
2766 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2767
2768 if (dropOverView != null) {
2769 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2770 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2771 return false;
2772 }
2773 }
2774
2775 boolean hasntMoved = false;
2776 if (mDragInfo != null) {
2777 hasntMoved = dropOverView == mDragInfo.cell;
2778 }
2779
2780 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2781 return false;
2782 }
2783
2784 boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2785 boolean willBecomeShortcut =
2786 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2787 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2788
2789 return (aboveShortcut && willBecomeShortcut);
2790 }
2791
2792 boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2793 float distance) {
2794 if (distance > mMaxDistanceForFolderCreation) return false;
2795 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2796
2797 if (dropOverView != null) {
2798 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2799 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2800 return false;
2801 }
2802 }
2803
2804 if (dropOverView instanceof FolderIcon) {
2805 FolderIcon fi = (FolderIcon) dropOverView;
2806 if (fi.acceptDrop(dragInfo)) {
2807 return true;
2808 }
2809 }
2810 return false;
2811 }
2812
2813 boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2814 int[] targetCell, float distance, boolean external, DragView dragView,
2815 Runnable postAnimationRunnable) {
2816 if (distance > mMaxDistanceForFolderCreation) return false;
2817 View v = target.getChildAt(targetCell[0], targetCell[1]);
2818
2819 boolean hasntMoved = false;
2820 if (mDragInfo != null) {
2821 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2822 hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2823 mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2824 }
2825
2826 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2827 mCreateUserFolderOnDrop = false;
2828 final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
2829
2830 boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2831 boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2832
2833 if (aboveShortcut && willBecomeShortcut) {
2834 ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2835 ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2836 // if the drag started here, we need to remove it from the workspace
2837 if (!external) {
2838 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2839 }
2840
2841 Rect folderLocation = new Rect();
2842 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2843 target.removeView(v);
2844
2845 FolderIcon fi =
2846 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
2847 destInfo.cellX = -1;
2848 destInfo.cellY = -1;
2849 sourceInfo.cellX = -1;
2850 sourceInfo.cellY = -1;
2851
2852 // If the dragView is null, we can't animate
2853 boolean animate = dragView != null;
2854 if (animate) {
2855 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2856 postAnimationRunnable);
2857 } else {
2858 fi.addItem(destInfo);
2859 fi.addItem(sourceInfo);
2860 }
2861 return true;
2862 }
2863 return false;
2864 }
2865
2866 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2867 float distance, DragObject d, boolean external) {
2868 if (distance > mMaxDistanceForFolderCreation) return false;
2869
2870 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2871 if (!mAddToExistingFolderOnDrop) return false;
2872 mAddToExistingFolderOnDrop = false;
2873
2874 if (dropOverView instanceof FolderIcon) {
2875 FolderIcon fi = (FolderIcon) dropOverView;
2876 if (fi.acceptDrop(d.dragInfo)) {
2877 fi.onDrop(d);
2878
2879 // if the drag started here, we need to remove it from the workspace
2880 if (!external) {
2881 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2882 }
2883 return true;
2884 }
2885 }
2886 return false;
2887 }
2888
2889 public void onDrop(final DragObject d) {
2890 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, mDrag🔵
2891 CellLayout dropTargetLayout = mDropToLayout;
2892 // We want the point to be mapped to the dragTarget.
2893 if (dropTargetLayout != null) {
2894 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2895 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2896 } else {
2897 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2898 }
2899 }
2900 int snapScreen = -1;
2901 boolean resizeOnDrop = false;
2902 if (d.dragSource != this) {
2903 final int[] touchXY = new int[]{ ((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVisualC🔵
2904 onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2905 } else if (mDragInfo != null) {
2906 final View cell = mDragInfo.cell;
2907 Runnable resizeRunnable = null;
2908 if ((dropTargetLayout != null) && (!d.cancelled)) {
2909 // Move internally
2910 boolean hasMovedLayouts = getParentCellLayoutForView(cell) != dropTargetLayout;
2911 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2912 long container = (hasMovedIntoHotseat) ? Favorites.CONTAINER_HOTSEAT : Favorites.CONTAINE🔵
2913 long screenId = (mTargetCell[0] < 0) ? mDragInfo.screenId : getIdForScreen(dropTargetLayo🔵
2914 int spanX = (mDragInfo != null) ? mDragInfo.spanX : 1;
2915 int spanY = (mDragInfo != null) ? mDragInfo.spanY : 1;
2916 // First we find the cell nearest to point at which the item is
2917 // dropped, without any consideration to whether there is an item there.
2918 mTargetCell = findNearestArea(((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVisual🔵
2919 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragView🔵
2920 // If the item being dropped is a shortcut and the nearest drop
2921 // cell also contains a shortcut, then create a folder with the two shortcuts.
2922 if ((!mInScrollArea) && createUserFolderIfNecessary(cell, container, dropTargetLayout, mT🔵
2923 return;
2924 }
2925 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, distance, d, fals🔵
2926 return;
2927 }
2928 // Aside from the special case where we're dropping a shortcut onto a shortcut,
2929 // we need to find the nearest cell location that is vacant
2930 ItemInfo item = ((ItemInfo) (d.dragInfo));
2931 int minSpanX = item.spanX;
2932 int minSpanY = item.spanY;
2933 if ((item.minSpanX > 0) && (item.minSpanY > 0)) {
2934 minSpanX = item.minSpanX;
2935 minSpanY = item.minSpanY;
2936 }
2937 int[] resultSpan = new int[2];
2938 mTargetCell = dropTargetLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) 🔵
2939 boolean foundCell = (mTargetCell[0] >= 0) && (mTargetCell[1] >= 0);
2940 // if the widget resizes on drop
2941 if ((foundCell && (cell instanceof AppWidgetHostView)) && ((resultSpan[0] != item.spanX) 🔵
2942 resizeOnDrop = true;
2943 item.spanX = resultSpan[0];
2944 item.spanY = resultSpan[1];
2945 AppWidgetHostView awhv = ((AppWidgetHostView) (cell));
2946 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0], resultSpa🔵
2947 }
2948 if ((getScreenIdForPageIndex(mCurrentPage) != screenId) && (!hasMovedIntoHotseat)) {
2949 snapScreen = getPageIndexForScreenId(screenId);
2950 snapToPage(snapScreen);
2951 }
2952 if (foundCell) {
2953 final ItemInfo info = ((ItemInfo) (cell.getTag()));
2954 if (hasMovedLayouts) {
2955 // Reparent the view
2956 CellLayout parentCell = getParentCellLayoutForView(cell);
2957 if (parentCell != null) {
2958 parentCell.removeView(cell);
2959 } else if (LauncherAppState.isDogfoodBuild()) {
2960 throw new NullPointerException("mDragInfo.cell has null parent");
2961 }
2962 addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX🔵
2963 }
2964 // update the item's position after drop
2965 CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) (cell.getLayoutParams()));
2966 lp.cellX = lp.tmpCellX = mTargetCell[0];
2967 lp.cellY = lp.tmpCellY = mTargetCell[1];
2968 lp.cellHSpan = item.spanX;
2969 lp.cellVSpan = item.spanY;
2970 lp.isLockedToGrid = true;
2971 if ((container != Favorites.CONTAINER_HOTSEAT) && (cell instanceof LauncherAppWidgetH🔵
2972 final CellLayout cellLayout = dropTargetLayout;
2973 // We post this call so that the widget has a chance to be placed
2974 // in its final location
2975 final LauncherAppWidgetHostView hostView = ((LauncherAppWidgetHostView) (cell));
2976 AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2977 if ((pinfo != null) && (pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE)) {
2978 final Runnable addResizeFrame = new Runnable() {
2979 public void run() {
2980 DragLayer dragLayer = mLauncher.getDragLayer();
2981 dragLayer.addResizeFrame(info, hostView, cellLayout);
2982 }
2983 };
2984 resizeRunnable = new Runnable() {
2985 public void run() {
2986 if (!isPageMoving()) {
2987 addResizeFrame.run();
2988 } else {
2989 mDelayedResizeRunnable = addResizeFrame;
2990 }
2991 }
2992 };
2993 }
2994 }
2995 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX, lp🔵
2996 } else {
2997 // If we can't find a drop location, we return the item to its original position
2998 CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) (cell.getLayoutParams()));
2999 mTargetCell[0] = lp.cellX;
3000 mTargetCell[1] = lp.cellY;
3001 CellLayout layout = ((CellLayout) (cell.getParent().getParent()));
3002 layout.markCellsAsOccupiedForView(cell);
3003 }
3004 }
3005 final CellLayout parent = ((CellLayout) (cell.getParent().getParent()));
3006 final Runnable finalResizeRunnable = resizeRunnable;
3007 // Prepare it to be animated into its new position
3008 // This must be called after the view has been re-parented
3009 final Runnable onCompleteRunnable = new Runnable() {
3010 @Override
3011 public void run() {
3012 mAnimatingViewIntoPlace = false;
3013 updateChildrenLayersEnabled(false);
3014 if (finalResizeRunnable != null) {
3015 finalResizeRunnable.run();
3016 }
3017 }
3018 };
3019 mAnimatingViewIntoPlace = true;
3020 if (d.dragView.hasDrawn()) {
3021 final ItemInfo info = ((ItemInfo) (cell.getTag()));
3022 if (info.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
3023 int animationType = (resizeOnDrop) ? ANIMATE_INTO_POSITION_AND_RESIZE : ANIMATE_INTO_🔵
3024 animateWidgetDrop(info, parent, d.dragView, onCompleteRunnable, animationType, cell, 🔵
3025 } else {
3026 int duration = (snapScreen < 0) ? -1 : ADJACENT_SCREEN_DROP_DURATION;
3027 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, onComple🔵
3028 }
3029 } else {
3030 d.deferDragViewCleanupPostAnimation = false;
3031 cell.setVisibility(VISIBLE);
3032 }
3033 parent.onDropChild(cell);
3034 }
3035 }
3036
3037 public void setFinalScrollForPageChange(int pageIndex) {
3038 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3039 if (cl != null) {
3040 mSavedScrollX = getScrollX();
3041 mSavedTranslationX = cl.getTranslationX();
3042 mSavedRotationY = cl.getRotationY();
3043 final int newX = getScrollForPage(pageIndex);
3044 setScrollX(newX);
3045 cl.setTranslationX(0f);
3046 cl.setRotationY(0f);
3047 }
3048 }
3049
3050 public void resetFinalScrollForPageChange(int pageIndex) {
3051 if (pageIndex >= 0) {
3052 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3053 setScrollX(mSavedScrollX);
3054 cl.setTranslationX(mSavedTranslationX);
3055 cl.setRotationY(mSavedRotationY);
3056 }
3057 }
3058
3059 public void getViewLocationRelativeToSelf(View v, int[] location) {
3060 getLocationInWindow(location);
3061 int x = location[0];
3062 int y = location[1];
3063
3064 v.getLocationInWindow(location);
3065 int vX = location[0];
3066 int vY = location[1];
3067
3068 location[0] = vX - x;
3069 location[1] = vY - y;
3070 }
3071
3072 public void onDragEnter(DragObject d) {
3073 mDragEnforcer.onDragEnter();
3074 mCreateUserFolderOnDrop = false;
3075 mAddToExistingFolderOnDrop = false;
3076 mDropToLayout = null;
3077 CellLayout layout = getCurrentDropLayout();
3078 setCurrentDropLayout(layout);
3079 setCurrentDragOverlappingLayout(layout);
3080 if (!workspaceInModalState()) {
3081 mLauncher.getDragLayer().showPageHints();
3082 }
3083 }
3084
3085 /** Return a rect that has the cellWidth/cellHeight (left, top), and
3086 * widthGap/heightGap (right, bottom) */
3087 static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
3088 LauncherAppState app = LauncherAppState.getInstance();
3089 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3090 Display display = launcher.getWindowManager().getDefaultDisplay();
3091 Point smallestSize = new Point();
3092 Point largestSize = new Point();
3093 display.getCurrentSizeRange(smallestSize, largestSize);
3094 int countX = ((int) (grid.numColumns));
3095 int countY = ((int) (grid.numRows));
3096 if (orientation == CellLayout.LANDSCAPE) {
3097 if (mLandscapeCellLayoutMetrics == null) {
3098 Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
3099 int width = (largestSize.x - padding.left) - padding.right;
3100 int height = (smallestSize.y - padding.top) - padding.bottom;
3101 mLandscapeCellLayoutMetrics = new Rect();
3102 mLandscapeCellLayoutMetrics.set(grid.calculateCellWidth(width, countX), grid.calculateCel🔵
3103 }
3104 return mLandscapeCellLayoutMetrics;
3105 } else if (orientation == CellLayout.PORTRAIT) {
3106 if (mPortraitCellLayoutMetrics == null) {
3107 Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
3108 int width = (smallestSize.x - padding.left) - padding.right;
3109 int height = (largestSize.y - padding.top) - padding.bottom;
3110 mPortraitCellLayoutMetrics = new Rect();
3111 mPortraitCellLayoutMetrics.set(grid.calculateCellWidth(width, countX), grid.calculateCell🔵
3112 }
3113 return mPortraitCellLayoutMetrics;
3114 }
3115 return null;
3116 }
3117
3118 public void onDragExit(DragObject d) {
3119 mDragEnforcer.onDragExit();
3120 // Here we store the final page that will be dropped to, if the workspace in fact
3121 // receives the drop
3122 if (mInScrollArea) {
3123 if (isPageMoving()) {
3124 // If the user drops while the page is scrolling, we should use that page as the
3125 // destination instead of the page that is being hovered over.
3126 mDropToLayout = ((CellLayout) (getPageAt(getNextPage())));
3127 } else {
3128 mDropToLayout = mDragOverlappingLayout;
3129 }
3130 } else {
3131 mDropToLayout = mDragTargetLayout;
3132 }
3133 if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
3134 mCreateUserFolderOnDrop = true;
3135 } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
3136 mAddToExistingFolderOnDrop = true;
3137 }
3138 // Reset the scroll area and previous drag target
3139 onResetScrollArea();
3140 setCurrentDropLayout(null);
3141 setCurrentDragOverlappingLayout(null);
3142 mSpringLoadedDragController.cancel();
3143 if (!mIsPageMoving) {
3144 hideOutlines();
3145 }
3146 mLauncher.getDragLayer().hidePageHints();
3147 }
3148
3149 void setCurrentDropLayout(CellLayout layout) {
3150 if (mDragTargetLayout != null) {
3151 mDragTargetLayout.revertTempState();
3152 mDragTargetLayout.onDragExit();
3153 }
3154 mDragTargetLayout = layout;
3155 if (mDragTargetLayout != null) {
3156 mDragTargetLayout.onDragEnter();
3157 }
3158 cleanupReorder(true);
3159 cleanupFolderCreation();
3160 setCurrentDropOverCell(-1, -1);
3161 }
3162
3163 void setCurrentDragOverlappingLayout(CellLayout layout) {
3164 if (mDragOverlappingLayout != null) {
3165 mDragOverlappingLayout.setIsDragOverlapping(false);
3166 }
3167 mDragOverlappingLayout = layout;
3168 if (mDragOverlappingLayout != null) {
3169 mDragOverlappingLayout.setIsDragOverlapping(true);
3170 }
3171 invalidate();
3172 }
3173
3174 void setCurrentDropOverCell(int x, int y) {
3175 if (x != mDragOverX || y != mDragOverY) {
3176 mDragOverX = x;
3177 mDragOverY = y;
3178 setDragMode(DRAG_MODE_NONE);
3179 }
3180 }
3181
3182 void setDragMode(int dragMode) {
3183 if (dragMode != mDragMode) {
3184 if (dragMode == DRAG_MODE_NONE) {
3185 cleanupAddToFolder();
3186 // We don't want to cancel the re-order alarm every time the target cell changes
3187 // as this feels to slow / unresponsive.
3188 cleanupReorder(false);
3189 cleanupFolderCreation();
3190 } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
3191 cleanupReorder(true);
3192 cleanupFolderCreation();
3193 } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
3194 cleanupAddToFolder();
3195 cleanupReorder(true);
3196 } else if (dragMode == DRAG_MODE_REORDER) {
3197 cleanupAddToFolder();
3198 cleanupFolderCreation();
3199 }
3200 mDragMode = dragMode;
3201 }
3202 }
3203
3204 private void cleanupFolderCreation() {
3205 if (mDragFolderRingAnimator != null) {
3206 mDragFolderRingAnimator.animateToNaturalState();
3207 mDragFolderRingAnimator = null;
3208 }
3209 mFolderCreationAlarm.setOnAlarmListener(null);
3210 mFolderCreationAlarm.cancelAlarm();
3211 }
3212
3213 private void cleanupAddToFolder() {
3214 if (mDragOverFolderIcon != null) {
3215 mDragOverFolderIcon.onDragExit(null);
3216 mDragOverFolderIcon = null;
3217 }
3218 }
3219
3220 private void cleanupReorder(boolean cancelAlarm) {
3221 // Any pending reorders are canceled
3222 if (cancelAlarm) {
3223 mReorderAlarm.cancelAlarm();
3224 }
3225 mLastReorderX = -1;
3226 mLastReorderY = -1;
3227 }
3228
3229 /*
3230 *
3231 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
3232 * coordinate space. The argument xy is modified with the return result.
3233 *
3234 * if cachedInverseMatrix is not null, this method will just use that matrix instead of
3235 * computing it itself; we use this to avoid redundant matrix inversions in
3236 * findMatchingPageForDragOver
3237 *
3238 */
3239 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
3240 xy[0] = xy[0] - v.getLeft();
3241 xy[1] = xy[1] - v.getTop();
3242 }
3243
3244 boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
3245 if (r == null) {
3246 r = new Rect();
3247 }
3248 mTempPt[0] = x;
3249 mTempPt[1] = y;
3250 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3251
3252 LauncherAppState app = LauncherAppState.getInstance();
3253 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3254 r = grid.getHotseatRect();
3255 if (r.contains(mTempPt[0], mTempPt[1])) {
3256 return true;
3257 }
3258 return false;
3259 }
3260
3261 void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
3262 mTempPt[0] = (int) xy[0];
3263 mTempPt[1] = (int) xy[1];
3264 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3265 mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
3266
3267 xy[0] = mTempPt[0];
3268 xy[1] = mTempPt[1];
3269 }
3270
3271 /*
3272 *
3273 * Convert the 2D coordinate xy from this CellLayout's coordinate space to
3274 * the parent View's coordinate space. The argument xy is modified with the return result.
3275 *
3276 */
3277 void mapPointFromChildToSelf(View v, float[] xy) {
3278 xy[0] += v.getLeft();
3279 xy[1] += v.getTop();
3280 }
3281
3282 static private float squaredDistance(float[] point1, float[] point2) {
3283 float distanceX = point1[0] - point2[0];
3284 float distanceY = point2[1] - point2[1];
3285 return distanceX * distanceX + distanceY * distanceY;
3286 }
3287
3288 /*
3289 *
3290 * This method returns the CellLayout that is currently being dragged to. In order to drag
3291 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
3292 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
3293 *
3294 * Return null if no CellLayout is currently being dragged over
3295 *
3296 */
3297 private CellLayout findMatchingPageForDragOver(
3298 DragView dragView, float originX, float originY, boolean exact) {
3299 // We loop through all the screens (ie CellLayouts) and see which ones overlap
3300 // with the item being dragged and then choose the one that's closest to the touch point
3301 final int screenCount = getChildCount();
3302 CellLayout bestMatchingScreen = null;
3303 float smallestDistSoFar = Float.MAX_VALUE;
3304
3305 for (int i = 0; i < screenCount; i++) {
3306 // The custom content screen is not a valid drag over option
3307 if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
3308 continue;
3309 }
3310
3311 CellLayout cl = (CellLayout) getChildAt(i);
3312
3313 final float[] touchXy = {originX, originY};
3314 // Transform the touch coordinates to the CellLayout's local coordinates
3315 // If the touch point is within the bounds of the cell layout, we can return immediately
3316 cl.getMatrix().invert(mTempInverseMatrix);
3317 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
3318
3319 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
3320 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
3321 return cl;
3322 }
3323
3324 if (!exact) {
3325 // Get the center of the cell layout in screen coordinates
3326 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
3327 cellLayoutCenter[0] = cl.getWidth()/2;
3328 cellLayoutCenter[1] = cl.getHeight()/2;
3329 mapPointFromChildToSelf(cl, cellLayoutCenter);
3330
3331 touchXy[0] = originX;
3332 touchXy[1] = originY;
3333
3334 // Calculate the distance between the center of the CellLayout
3335 // and the touch point
3336 float dist = squaredDistance(touchXy, cellLayoutCenter);
3337
3338 if (dist < smallestDistSoFar) {
3339 smallestDistSoFar = dist;
3340 bestMatchingScreen = cl;
3341 }
3342 }
3343 }
3344 return bestMatchingScreen;
3345 }
3346
3347 // This is used to compute the visual center of the dragView. This point is then
3348 // used to visualize drop locations and determine where to drop an item. The idea is that
3349 // the visual center represents the user's interpretation of where the item is, and hence
3350 // is the appropriate point to use when determining drop location.
3351 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
3352 DragView dragView, float[] recycle) {
3353 float res[];
3354 if (recycle == null) {
3355 res = new float[2];
3356 } else {
3357 res = recycle;
3358 }
3359
3360 // First off, the drag view has been shifted in a way that is not represented in the
3361 // x and y values or the x/yOffsets. Here we account for that shift.
3362 x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
3363 y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
3364
3365 // These represent the visual top and left of drag view if a dragRect was provided.
3366 // If a dragRect was not provided, then they correspond to the actual view left and
3367 // top, as the dragRect is in that case taken to be the entire dragView.
3368 // R.dimen.dragViewOffsetY.
3369 int left = x - xOffset;
3370 int top = y - yOffset;
3371
3372 // In order to find the visual center, we shift by half the dragRect
3373 res[0] = left + dragView.getDragRegion().width() / 2;
3374 res[1] = top + dragView.getDragRegion().height() / 2;
3375
3376 return res;
3377 }
3378
3379 private boolean isDragWidget(DragObject d) {
3380 return (d.dragInfo instanceof LauncherAppWidgetInfo ||
3381 d.dragInfo instanceof PendingAddWidgetInfo);
3382 }
3383
3384 private boolean isExternalDragWidget(DragObject d) {
3385 return d.dragSource != this && isDragWidget(d);
3386 }
3387
3388 public void onDragOver(DragObject d) {
3389 // Skip drag over events while we are dragging over side pages
3390 if (mInScrollArea || (!transitionStateShouldAllowDrop())) {
3391 return;
3392 }
3393 Rect r = new Rect();
3394 CellLayout layout = null;
3395 ItemInfo item = ((ItemInfo) (d.dragInfo));
3396 if (item == null) {
3397 if (LauncherAppState.isDogfoodBuild()) {
3398 throw new NullPointerException("DragObject has null info");
3399 }
3400 return;
3401 }
3402 // Ensure that we have proper spans for the item that we are dropping
3403 if ((item.spanX < 0) || (item.spanY < 0)) {
3404 throw new RuntimeException("Improper spans found");
3405 }
3406 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, mDrag🔵
3407 final View child = (mDragInfo == null) ? null : mDragInfo.cell;
3408 // Identify whether we have dragged over a side page
3409 if (workspaceInModalState()) {
3410 if ((mLauncher.getHotseat() != null) && (!isExternalDragWidget(d))) {
3411 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3412 layout = mLauncher.getHotseat().getLayout();
3413 }
3414 }
3415 if (layout == null) {
3416 layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
3417 }
3418 if (layout != mDragTargetLayout) {
3419 setCurrentDropLayout(layout);
3420 setCurrentDragOverlappingLayout(layout);
3421 boolean isInSpringLoadedMode = mState == State.SPRING_LOADED;
3422 if (isInSpringLoadedMode) {
3423 if (mLauncher.isHotseatLayout(layout)) {
3424 mSpringLoadedDragController.cancel();
3425 } else {
3426 mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3427 }
3428 }
3429 }
3430 } else {
3431 // Test to see if we are over the hotseat otherwise just use the current page
3432 if ((mLauncher.getHotseat() != null) && (!isDragWidget(d))) {
3433 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3434 layout = mLauncher.getHotseat().getLayout();
3435 }
3436 }
3437 if (layout == null) {
3438 layout = getCurrentDropLayout();
3439 }
3440 if (layout != mDragTargetLayout) {
3441 setCurrentDropLayout(layout);
3442 setCurrentDragOverlappingLayout(layout);
3443 }
3444 }
3445 // Handle the drag over
3446 if (mDragTargetLayout != null) {
3447 // We want the point to be mapped to the dragTarget.
3448 if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3449 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3450 } else {
3451 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
3452 }
3453 ItemInfo info = ((ItemInfo) (d.dragInfo));
3454 int minSpanX = item.spanX;
3455 int minSpanY = item.spanY;
3456 if ((item.minSpanX > 0) && (item.minSpanY > 0)) {
3457 minSpanX = item.minSpanX;
3458 minSpanY = item.minSpanY;
3459 }
3460 mTargetCell = findNearestArea(((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVisualCent🔵
3461 int reorderX = mTargetCell[0];
3462 int reorderY = mTargetCell[1];
3463 setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3464 float targetCellDistance = mDragTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], mD🔵
3465 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
3466 manageFolderFeedback(info, mDragTargetLayout, mTargetCell, targetCellDistance, dragOverView);
3467 boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied(((int) (mDragVi🔵
3468 if (!nearestDropOccupied) {
3469 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, ((int) (mDragViewVisualCente🔵
3470 } else if ((((mDragMode == DRAG_MODE_NONE) || (mDragMode == DRAG_MODE_REORDER)) && (!mReorder🔵
3471 int[] resultSpan = new int[2];
3472 mDragTargetLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVis🔵
3473 // Otherwise, if we aren't adding to or creating a folder and there's no pending
3474 // reorder, then we schedule a reorder
3475 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter, minSpanX,🔵
3476 mReorderAlarm.setOnAlarmListener(listener);
3477 mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3478 }
3479 if (((mDragMode == DRAG_MODE_CREATE_FOLDER) || (mDragMode == DRAG_MODE_ADD_TO_FOLDER)) || (!n🔵
3480 if (mDragTargetLayout != null) {
3481 mDragTargetLayout.revertTempState();
3482 }
3483 }
3484 }
3485 }
3486
3487 private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
3488 int[] targetCell, float distance, View dragOverView) {
3489 boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
3490 false);
3491
3492 if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3493 !mFolderCreationAlarm.alarmPending()) {
3494 mFolderCreationAlarm.setOnAlarmListener(new
3495 FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
3496 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3497 return;
3498 }
3499
3500 boolean willAddToFolder =
3501 willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
3502
3503 if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3504 mDragOverFolderIcon = ((FolderIcon) dragOverView);
3505 mDragOverFolderIcon.onDragEnter(info);
3506 if (targetLayout != null) {
3507 targetLayout.clearDragOutlines();
3508 }
3509 setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3510 return;
3511 }
3512
3513 if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3514 setDragMode(DRAG_MODE_NONE);
3515 }
3516 if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3517 setDragMode(DRAG_MODE_NONE);
3518 }
3519
3520 return;
3521 }
3522
3523 class FolderCreationAlarmListener implements OnAlarmListener {
3524 CellLayout layout;
3525
3526 int cellX;
3527
3528 int cellY;
3529
3530 public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3531 this.layout = layout;
3532 this.cellX = cellX;
3533 this.cellY = cellY;
3534 }
3535
3536 public void onAlarm(Alarm alarm) {
3537 if (mDragFolderRingAnimator != null) {
3538 // This shouldn't happen ever, but just in case, make sure we clean up the mess.
3539 mDragFolderRingAnimator.animateToNaturalState();
3540 }
3541 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
3542 mDragFolderRingAnimator.setCell(cellX, cellY);
3543 mDragFolderRingAnimator.setCellLayout(layout);
3544 mDragFolderRingAnimator.animateToAcceptState();
3545 layout.showFolderAccept(mDragFolderRingAnimator);
3546 layout.clearDragOutlines();
3547 setDragMode(DRAG_MODE_CREATE_FOLDER);
3548 }
3549 }
3550
3551 class ReorderAlarmListener implements OnAlarmListener {
3552 float[] dragViewCenter;
3553
3554 int minSpanX;
3555
3556 int minSpanY;
3557
3558 int spanX;
3559
3560 int spanY;
3561
3562 DragView dragView;
3563
3564 View child;
3565
3566 public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX, int sp🔵
3567 this.dragViewCenter = dragViewCenter;
3568 this.minSpanX = minSpanX;
3569 this.minSpanY = minSpanY;
3570 this.spanX = spanX;
3571 this.spanY = spanY;
3572 this.child = child;
3573 this.dragView = dragView;
3574 }
3575
3576 public void onAlarm(Alarm alarm) {
3577 int[] resultSpan = new int[2];
3578 mTargetCell = findNearestArea(((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVisualCent🔵
3579 mLastReorderX = mTargetCell[0];
3580 mLastReorderY = mTargetCell[1];
3581 mTargetCell = mDragTargetLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) (mD🔵
3582 if ((mTargetCell[0] < 0) || (mTargetCell[1] < 0)) {
3583 mDragTargetLayout.revertTempState();
3584 } else {
3585 setDragMode(DRAG_MODE_REORDER);
3586 }
3587 boolean resize = (resultSpan[0] != spanX) || (resultSpan[1] != spanY);
3588 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, ((int) (mDragViewVisualCenter[0]🔵
3589 }
3590 }
3591
3592 @Override
3593 public void getHitRectRelativeToDragLayer(Rect outRect) {
3594 // We want the workspace to have the whole area of the display (it will find the correct
3595 // cell layout to drop to in the existing drag/drop logic.
3596 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3597 }
3598
3599 /**
3600 * Add the item specified by dragInfo to the given layout.
3601 * @return true if successful
3602 */
3603 public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
3604 if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
3605 onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
3606 return true;
3607 }
3608 mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
3609 return false;
3610 }
3611
3612 private void onDropExternal(int[] touchXY, Object dragInfo,
3613 CellLayout cellLayout, boolean insertAtFirst) {
3614 onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
3615 }
3616
3617 /**
3618 * Drop an item that didn't originate on one of the workspace screens.
3619 * It may have come from Launcher (e.g. from all apps or customize), or it may have
3620 * come from another app altogether.
3621 *
3622 * NOTE: This can also be called when we are outside of a drag event, when we want
3623 * to add an item to one of the workspace screens.
3624 */
3625 private void onDropExternal(final int[] touchXY, final Object dragInfo, final CellLayout cellLayout, 🔵
3626 final Runnable exitSpringLoadedRunnable = new Runnable() {
3627 @Override
3628 public void run() {
3629 mLauncher.exitSpringLoadedDragModeDelayed(true, Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIM🔵
3630 }
3631 };
3632 ItemInfo info = ((ItemInfo) (dragInfo));
3633 int spanX = info.spanX;
3634 int spanY = info.spanY;
3635 if (mDragInfo != null) {
3636 spanX = mDragInfo.spanX;
3637 spanY = mDragInfo.spanY;
3638 }
3639 final long container = (mLauncher.isHotseatLayout(cellLayout)) ? Favorites.CONTAINER_HOTSEAT : Fa🔵
3640 final long screenId = getIdForScreen(cellLayout);
3641 if (((!mLauncher.isHotseatLayout(cellLayout)) && (screenId != getScreenIdForPageIndex(mCurrentPag🔵
3642 snapToScreenId(screenId, null);
3643 }
3644 if (info instanceof PendingAddItemInfo) {
3645 final PendingAddItemInfo pendingInfo = ((PendingAddItemInfo) (dragInfo));
3646 boolean findNearestVacantCell = true;
3647 if (pendingInfo.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
3648 mTargetCell = findNearestArea(((int) (touchXY[0])), ((int) (touchXY[1])), spanX, spanY, c🔵
3649 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisual🔵
3650 if (willCreateUserFolder(((ItemInfo) (d.dragInfo)), cellLayout, mTargetCell, distance, tr🔵
3651 findNearestVacantCell = false;
3652 }
3653 }
3654 final ItemInfo item = ((ItemInfo) (d.dragInfo));
3655 boolean updateWidgetSize = false;
3656 if (findNearestVacantCell) {
3657 int minSpanX = item.spanX;
3658 int minSpanY = item.spanY;
3659 if ((item.minSpanX > 0) && (item.minSpanY > 0)) {
3660 minSpanX = item.minSpanX;
3661 minSpanY = item.minSpanY;
3662 }
3663 int[] resultSpan = new int[2];
3664 mTargetCell = cellLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) (mDrag🔵
3665 if ((resultSpan[0] != item.spanX) || (resultSpan[1] != item.spanY)) {
3666 updateWidgetSize = true;
3667 }
3668 item.spanX = resultSpan[0];
3669 item.spanY = resultSpan[1];
3670 }
3671 Runnable onAnimationCompleteRunnable = new Runnable() {
3672 @Override
3673 public void run() {
3674 // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
3675 // adding an item that may not be dropped right away (due to a config activity)
3676 // we defer the removal until the activity returns.
3677 deferRemoveExtraEmptyScreen();
3678 // When dragging and dropping from customization tray, we deal with creating
3679 // widgets/shortcuts/folders in a slightly different way
3680 switch (pendingInfo.itemType) {
3681 case Favorites.ITEM_TYPE_APPWIDGET :
3682 int[] span = new int[2];
3683 span[0] = item.spanX;
3684 span[1] = item.spanY;
3685 mLauncher.addAppWidgetFromDrop(((PendingAddWidgetInfo) (pendingInfo)), contai🔵
3686 break;
3687 case Favorites.ITEM_TYPE_SHORTCUT :
3688 mLauncher.processShortcutFromDrop(pendingInfo.componentName, container, scree🔵
3689 break;
3690 default :
3691 throw new IllegalStateException("Unknown item type: " + pendingInfo.itemType)🔵
3692 }
3693 }
3694 };
3695 View finalView = (pendingInfo.itemType == Favorites.ITEM_TYPE_APPWIDGET) ? ((PendingAddWidget🔵
3696 if ((finalView instanceof AppWidgetHostView) && updateWidgetSize) {
3697 AppWidgetHostView awhv = ((AppWidgetHostView) (finalView));
3698 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX, item.spanY);
3699 }
3700 int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3701 if ((pendingInfo.itemType == Favorites.ITEM_TYPE_APPWIDGET) && (((PendingAddWidgetInfo) (pend🔵
3702 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3703 }
3704 animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable, animationStyle, 🔵
3705 } else {
3706 // This is for other drag/drop cases, like dragging from All Apps
3707 View view = null;
3708 switch (info.itemType) {
3709 case Favorites.ITEM_TYPE_APPLICATION :
3710 case Favorites.ITEM_TYPE_SHORTCUT :
3711 if ((info.container == NO_ID) && (info instanceof AppInfo)) {
3712 // Came from all apps -- make a copy
3713 info = new ShortcutInfo(((AppInfo) (info)));
3714 }
3715 view = mLauncher.createShortcut(R.layout.application, cellLayout, ((ShortcutInfo) (in🔵
3716 break;
3717 case Favorites.ITEM_TYPE_FOLDER :
3718 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, ((FolderInfo) 🔵
3719 break;
3720 default :
3721 throw new IllegalStateException("Unknown item type: " + info.itemType);
3722 }
3723 // First we find the cell nearest to point at which the item is
3724 // dropped, without any consideration to whether there is an item there.
3725 if (touchXY != null) {
3726 mTargetCell = findNearestArea(((int) (touchXY[0])), ((int) (touchXY[1])), spanX, spanY, c🔵
3727 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisual🔵
3728 d.postAnimationRunnable = exitSpringLoadedRunnable;
3729 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance, true,🔵
3730 return;
3731 }
3732 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d, true)) {
3733 return;
3734 }
3735 }
3736 if (touchXY != null) {
3737 // when dragging and dropping, just find the closest free spot
3738 mTargetCell = cellLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) (mDrag🔵
3739 } else {
3740 cellLayout.findCellForSpan(mTargetCell, 1, 1);
3741 }
3742 // Add the item to DB before adding to screen ensures that the container and other
3743 // values of the info is properly updated.
3744 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, mTargetCell[0], m🔵
3745 addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX, info.spanY🔵
3746 cellLayout.onDropChild(view);
3747 cellLayout.getShortcutsAndWidgets().measureChild(view);
3748 if (d.dragView != null) {
3749 // We wrap the animation call in the temporary set and reset of the current
3750 // cellLayout to its final transform -- this means we animate the drag view to
3751 // the correct final location.
3752 setFinalTransitionTransform(cellLayout);
3753 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, exitSpringLoadedRunnab🔵
3754 resetTransitionTransform(cellLayout);
3755 }
3756 }
3757 }
3758
3759 public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3760 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX, widgetInfo.spanY🔵
3761 int visibility = layout.getVisibility();
3762 layout.setVisibility(VISIBLE);
3763 int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3764 int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3765 Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1], Bitmap.Config.ARGB_8888);
3766 mCanvas.setBitmap(b);
3767 layout.measure(width, height);
3768 layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3769 layout.draw(mCanvas);
3770 mCanvas.setBitmap(null);
3771 layout.setVisibility(visibility);
3772 return b;
3773 }
3774
3775 private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3776 DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
3777 boolean external, boolean scale) {
3778 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3779 // location and size on the home screen.
3780 int spanX = info.spanX;
3781 int spanY = info.spanY;
3782
3783 Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
3784 loc[0] = r.left;
3785 loc[1] = r.top;
3786
3787 setFinalTransitionTransform(layout);
3788 float cellLayoutScale =
3789 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
3790 resetTransitionTransform(layout);
3791
3792 float dragViewScaleX;
3793 float dragViewScaleY;
3794 if (scale) {
3795 dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3796 dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3797 } else {
3798 dragViewScaleX = 1f;
3799 dragViewScaleY = 1f;
3800 }
3801
3802 // The animation will scale the dragView about its center, so we need to center about
3803 // the final location.
3804 loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
3805 loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3806
3807 scaleXY[0] = dragViewScaleX * cellLayoutScale;
3808 scaleXY[1] = dragViewScaleY * cellLayoutScale;
3809 }
3810
3811 public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
3812 final Runnable onCompleteRunnable, int animationType, final View finalView,
3813 boolean external) {
3814 Rect from = new Rect();
3815 mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3816
3817 int[] finalPos = new int[2];
3818 float scaleXY[] = new float[2];
3819 boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
3820 getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3821 external, scalePreview);
3822
3823 Resources res = mLauncher.getResources();
3824 final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3825
3826 // In the case where we've prebound the widget, we remove it from the DragLayer
3827 if (finalView instanceof AppWidgetHostView && external) {
3828 Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
3829 mLauncher.getDragLayer().removeView(finalView);
3830 }
3831 if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3832 Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3833 dragView.setCrossFadeBitmap(crossFadeBitmap);
3834 dragView.crossFade((int) (duration * 0.8f));
3835 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
3836 scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
3837 }
3838
3839 DragLayer dragLayer = mLauncher.getDragLayer();
3840 if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3841 mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3842 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3843 } else {
3844 int endStyle;
3845 if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3846 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3847 } else {
3848 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
3849 }
3850
3851 Runnable onComplete = new Runnable() {
3852 @Override
3853 public void run() {
3854 if (finalView != null) {
3855 finalView.setVisibility(VISIBLE);
3856 }
3857 if (onCompleteRunnable != null) {
3858 onCompleteRunnable.run();
3859 }
3860 }
3861 };
3862 dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3863 finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3864 duration, this);
3865 }
3866 }
3867
3868 public void setFinalTransitionTransform(CellLayout layout) {
3869 if (isSwitchingState()) {
3870 mCurrentScale = getScaleX();
3871 setScaleX(mNewScale);
3872 setScaleY(mNewScale);
3873 }
3874 }
3875
3876 public void resetTransitionTransform(CellLayout layout) {
3877 if (isSwitchingState()) {
3878 setScaleX(mCurrentScale);
3879 setScaleY(mCurrentScale);
3880 }
3881 }
3882
3883 /**
3884 * Return the current {@link CellLayout}, correctly picking the destination
3885 * screen while a scroll is in progress.
3886 */
3887 public CellLayout getCurrentDropLayout() {
3888 return (CellLayout) getChildAt(getNextPage());
3889 }
3890
3891 /**
3892 * Return the current CellInfo describing our current drag; this method exists
3893 * so that Launcher can sync this object with the correct info when the activity is created/
3894 * destroyed
3895 *
3896 */
3897 public CellLayout.CellInfo getDragInfo() {
3898 return mDragInfo;
3899 }
3900
3901 public int getCurrentPageOffsetFromCustomContent() {
3902 return getNextPage() - numCustomPages();
3903 }
3904
3905 /**
3906 * Calculate the nearest cell where the given object would be dropped.
3907 *
3908 * pixelX and pixelY should be in the coordinate system of layout
3909 */
3910 private int[] findNearestArea(int pixelX, int pixelY,
3911 int spanX, int spanY, CellLayout layout, int[] recycle) {
3912 return layout.findNearestArea(
3913 pixelX, pixelY, spanX, spanY, recycle);
3914 }
3915
3916 void setup(DragController dragController) {
3917 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3918 mDragController = dragController;
3919
3920 // hardware layers on children are enabled on startup, but should be disabled until
3921 // needed
3922 updateChildrenLayersEnabled(false);
3923 }
3924
3925 /**
3926 * Called at the end of a drag which originated on the workspace.
3927 */
3928 public void onDropCompleted(final View target, final DragObject d, final boolean isFlingToDelete, fin🔵
3929 if (mDeferDropAfterUninstall) {
3930 mDeferredAction = new Runnable() {
3931 public void run() {
3932 onDropCompleted(target, d, isFlingToDelete, success);
3933 mDeferredAction = null;
3934 }
3935 };
3936 return;
3937 }
3938 boolean beingCalledAfterUninstall = mDeferredAction != null;
3939 if (success && (!(beingCalledAfterUninstall && (!mUninstallSuccessful)))) {
3940 if ((target != this) && (mDragInfo != null)) {
3941 CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
3942 if (parentCell != null) {
3943 parentCell.removeView(mDragInfo.cell);
3944 } else if (LauncherAppState.isDogfoodBuild()) {
3945 throw new NullPointerException("mDragInfo.cell has null parent");
3946 }
3947 if (mDragInfo.cell instanceof DropTarget) {
3948 mDragController.removeDropTarget(((DropTarget) (mDragInfo.cell)));
3949 }
3950 }
3951 } else if (mDragInfo != null) {
3952 CellLayout cellLayout;
3953 if (mLauncher.isHotseatLayout(target)) {
3954 cellLayout = mLauncher.getHotseat().getLayout();
3955 } else {
3956 cellLayout = getScreenWithId(mDragInfo.screenId);
3957 }
3958 if ((cellLayout == null) && LauncherAppState.isDogfoodBuild()) {
3959 throw new RuntimeException("Invalid state: cellLayout == null in " + "Workspace#onDropCom🔵
3960 }
3961 if (cellLayout != null) {
3962 cellLayout.onDropChild(mDragInfo.cell);
3963 }
3964 }
3965 if ((d.cancelled || (beingCalledAfterUninstall && (!mUninstallSuccessful))) && (mDragInfo.cell !=🔵
3966 mDragInfo.cell.setVisibility(VISIBLE);
3967 }
3968 mDragOutline = null;
3969 mDragInfo = null;
3970 }
3971
3972 public void deferCompleteDropAfterUninstallActivity() {
3973 mDeferDropAfterUninstall = true;
3974 }
3975
3976 /// maybe move this into a smaller part
3977 public void onUninstallActivityReturned(boolean success) {
3978 mDeferDropAfterUninstall = false;
3979 mUninstallSuccessful = success;
3980 if (mDeferredAction != null) {
3981 mDeferredAction.run();
3982 }
3983 }
3984
3985 void updateItemLocationsInDatabase(CellLayout cl) {
3986 int count = cl.getShortcutsAndWidgets().getChildCount();
3987
3988 long screenId = getIdForScreen(cl);
3989 int container = Favorites.CONTAINER_DESKTOP;
3990
3991 if (mLauncher.isHotseatLayout(cl)) {
3992 screenId = -1;
3993 container = Favorites.CONTAINER_HOTSEAT;
3994 }
3995
3996 for (int i = 0; i < count; i++) {
3997 View v = cl.getShortcutsAndWidgets().getChildAt(i);
3998 ItemInfo info = (ItemInfo) v.getTag();
3999 // Null check required as the AllApps button doesn't have an item info
4000 if (info != null && info.requiresDbUpdate) {
4001 info.requiresDbUpdate = false;
4002 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
4003 info.cellY, info.spanX, info.spanY);
4004 }
4005 }
4006 }
4007
4008 ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplic🔵
4009 ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
4010 getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, fals🔵
4011 int count = getChildCount();
4012 for (int i = 0; i < count; i++) {
4013 CellLayout cl = (CellLayout) getChildAt(i);
4014 getUniqueIntents(cl, uniqueIntents, duplicates, false);
4015 }
4016 return uniqueIntents;
4017 }
4018
4019 void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
4020 ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
4021 int count = cl.getShortcutsAndWidgets().getChildCount();
4022
4023 ArrayList<View> children = new ArrayList<View>();
4024 for (int i = 0; i < count; i++) {
4025 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4026 children.add(v);
4027 }
4028
4029 for (int i = 0; i < count; i++) {
4030 View v = children.get(i);
4031 ItemInfo info = (ItemInfo) v.getTag();
4032 // Null check required as the AllApps button doesn't have an item info
4033 if (info instanceof ShortcutInfo) {
4034 ShortcutInfo si = (ShortcutInfo) info;
4035 ComponentName cn = si.intent.getComponent();
4036
4037 Uri dataUri = si.intent.getData();
4038 // If dataUri is not null / empty or if this component isn't one that would
4039 // have previously showed up in the AllApps list, then this is a widget-type
4040 // shortcut, so ignore it.
4041 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4042 continue;
4043 }
4044
4045 if (!uniqueIntents.contains(cn)) {
4046 uniqueIntents.add(cn);
4047 } else {
4048 if (stripDuplicates) {
4049 cl.removeViewInLayout(v);
4050 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4051 }
4052 if (duplicates != null) {
4053 duplicates.add(cn);
4054 }
4055 }
4056 }
4057 if (v instanceof FolderIcon) {
4058 FolderIcon fi = (FolderIcon) v;
4059 ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
4060 for (int j = 0; j < items.size(); j++) {
4061 if (items.get(j).getTag() instanceof ShortcutInfo) {
4062 ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
4063 ComponentName cn = si.intent.getComponent();
4064
4065 Uri dataUri = si.intent.getData();
4066 // If dataUri is not null / empty or if this component isn't one that would
4067 // have previously showed up in the AllApps list, then this is a widget-type
4068 // shortcut, so ignore it.
4069 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4070 continue;
4071 }
4072
4073 if (!uniqueIntents.contains(cn)) {
4074 uniqueIntents.add(cn);
4075 } else {
4076 if (stripDuplicates) {
4077 fi.getFolderInfo().remove(si);
4078 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4079 }
4080 if (duplicates != null) {
4081 duplicates.add(cn);
4082 }
4083 }
4084 }
4085 }
4086 }
4087 }
4088 }
4089
4090 void saveWorkspaceToDb() {
4091 saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
4092 int count = getChildCount();
4093 for (int i = 0; i < count; i++) {
4094 CellLayout cl = (CellLayout) getChildAt(i);
4095 saveWorkspaceScreenToDb(cl);
4096 }
4097 }
4098
4099 void saveWorkspaceScreenToDb(CellLayout cl) {
4100 int count = cl.getShortcutsAndWidgets().getChildCount();
4101
4102 long screenId = getIdForScreen(cl);
4103 int container = Favorites.CONTAINER_DESKTOP;
4104
4105 Hotseat hotseat = mLauncher.getHotseat();
4106 if (mLauncher.isHotseatLayout(cl)) {
4107 screenId = -1;
4108 container = Favorites.CONTAINER_HOTSEAT;
4109 }
4110
4111 for (int i = 0; i < count; i++) {
4112 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4113 ItemInfo info = (ItemInfo) v.getTag();
4114 // Null check required as the AllApps button doesn't have an item info
4115 if (info != null) {
4116 int cellX = info.cellX;
4117 int cellY = info.cellY;
4118 if (container == Favorites.CONTAINER_HOTSEAT) {
4119 cellX = hotseat.getCellXFromOrder((int) info.screenId);
4120 cellY = hotseat.getCellYFromOrder((int) info.screenId);
4121 }
4122 LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
4123 cellY, false);
4124 }
4125 if (v instanceof FolderIcon) {
4126 FolderIcon fi = (FolderIcon) v;
4127 fi.getFolder().addItemLocationsInDatabase();
4128 }
4129 }
4130 }
4131
4132 @Override
4133 public float getIntrinsicIconScaleFactor() {
4134 return 1f;
4135 }
4136
4137 @Override
4138 public boolean supportsFlingToDelete() {
4139 return true;
4140 }
4141
4142 @Override
4143 public boolean supportsAppInfoDropTarget() {
4144 return false;
4145 }
4146
4147 @Override
4148 public boolean supportsDeleteDropTarget() {
4149 return true;
4150 }
4151
4152 @Override
4153 public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
4154 // Do nothing
4155 }
4156
4157 @Override
4158 public void onFlingToDeleteCompleted() {
4159 // Do nothing
4160 }
4161
4162 public boolean isDropEnabled() {
4163 return true;
4164 }
4165
4166 @Override
4167 protected void onRestoreInstanceState(Parcelable state) {
4168 super.onRestoreInstanceState(state);
4169 Launcher.setScreen(mCurrentPage);
4170 }
4171
4172 @Override
4173 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
4174 // We don't dispatch restoreInstanceState to our children using this code path.
4175 // Some pages will be restored immediately as their items are bound immediately, and
4176 // others we will need to wait until after their items are bound.
4177 mSavedStates = container;
4178 }
4179
4180 public void restoreInstanceStateForChild(int child) {
4181 if (mSavedStates != null) {
4182 mRestoredPages.add(child);
4183 CellLayout cl = (CellLayout) getChildAt(child);
4184 if (cl != null) {
4185 cl.restoreInstanceState(mSavedStates);
4186 }
4187 }
4188 }
4189
4190 public void restoreInstanceStateForRemainingPages() {
4191 int count = getChildCount();
4192 for (int i = 0; i < count; i++) {
4193 if (!mRestoredPages.contains(i)) {
4194 restoreInstanceStateForChild(i);
4195 }
4196 }
4197 mRestoredPages.clear();
4198 mSavedStates = null;
4199 }
4200
4201 @Override
4202 public void scrollLeft() {
4203 if ((!workspaceInModalState()) && (!mIsSwitchingState)) {
4204 super.scrollLeft();
4205 }
4206 Folder openFolder = getOpenFolder();
4207 if (openFolder != null) {
4208 openFolder.completeDragExit();
4209 }
4210 }
4211
4212 @Override
4213 public void scrollRight() {
4214 if ((!workspaceInModalState()) && (!mIsSwitchingState)) {
4215 super.scrollRight();
4216 }
4217 Folder openFolder = getOpenFolder();
4218 if (openFolder != null) {
4219 openFolder.completeDragExit();
4220 }
4221 }
4222
4223 @Override
4224 public boolean onEnterScrollArea(int x, int y, int direction) {
4225 // Ignore the scroll area if we are dragging over the hot seat
4226 boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
4227 if ((mLauncher.getHotseat() != null) && isPortrait) {
4228 Rect r = new Rect();
4229 mLauncher.getHotseat().getHitRect(r);
4230 if (r.contains(x, y)) {
4231 return false;
4232 }
4233 }
4234 boolean result = false;
4235 if (((!workspaceInModalState()) && (!mIsSwitchingState)) && (getOpenFolder() == null)) {
4236 mInScrollArea = true;
4237 final int page = getNextPage() + (direction == DragController.SCROLL_LEFT ? -1 : 1);
4238 // We always want to exit the current layout to ensure parity of enter / exit
4239 setCurrentDropLayout(null);
4240 if ((0 <= page) && (page < getChildCount())) {
4241 // Ensure that we are not dragging over to the custom content screen
4242 if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
4243 return false;
4244 }
4245 CellLayout layout = ((CellLayout) (getChildAt(page)));
4246 setCurrentDragOverlappingLayout(layout);
4247 // Workspace is responsible for drawing the edge glow on adjacent pages,
4248 // so we need to redraw the workspace when this may have changed.
4249 invalidate();
4250 result = true;
4251 }
4252 }
4253 return result;
4254 }
4255
4256 @Override
4257 public boolean onExitScrollArea() {
4258 boolean result = false;
4259 if (mInScrollArea) {
4260 invalidate();
4261 CellLayout layout = getCurrentDropLayout();
4262 setCurrentDropLayout(layout);
4263 setCurrentDragOverlappingLayout(layout);
4264
4265 result = true;
4266 mInScrollArea = false;
4267 }
4268 return result;
4269 }
4270
4271 private void onResetScrollArea() {
4272 setCurrentDragOverlappingLayout(null);
4273 mInScrollArea = false;
4274 }
4275
4276 /**
4277 * Returns a specific CellLayout
4278 */
4279 CellLayout getParentCellLayoutForView(View v) {
4280 ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
4281 for (CellLayout layout : layouts) {
4282 if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
4283 return layout;
4284 }
4285 }
4286 return null;
4287 }
4288
4289 /**
4290 * Returns a list of all the CellLayouts in the workspace.
4291 */
4292 ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
4293 ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
4294 int screenCount = getChildCount();
4295 for (int screen = 0; screen < screenCount; screen++) {
4296 layouts.add(((CellLayout) getChildAt(screen)));
4297 }
4298 if (mLauncher.getHotseat() != null) {
4299 layouts.add(mLauncher.getHotseat().getLayout());
4300 }
4301 return layouts;
4302 }
4303
4304 /**
4305 * We should only use this to search for specific children. Do not use this method to modify
4306 * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
4307 * the hotseat and workspace pages
4308 */
4309 ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
4310 ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4311 new ArrayList<ShortcutAndWidgetContainer>();
4312 int screenCount = getChildCount();
4313 for (int screen = 0; screen < screenCount; screen++) {
4314 childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
4315 }
4316 if (mLauncher.getHotseat() != null) {
4317 childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
4318 }
4319 return childrenLayouts;
4320 }
4321
4322 public Folder getFolderForTag(final Object tag) {
4323 return ((Folder) (getFirstMatch(new ItemOperator() {
4324 @Override
4325 public boolean evaluate(ItemInfo info, View v, View parent) {
4326 return ((v instanceof Folder) && (((Folder) (v)).getInfo() == tag)) && ((Folder) (v)).get🔵
4327 }
4328 })));
4329 }
4330
4331 public View getViewForTag(final Object tag) {
4332 return getFirstMatch(new ItemOperator() {
4333
4334 @Override
4335 public boolean evaluate(ItemInfo info, View v, View parent) {
4336 return info == tag;
4337 }
4338 });
4339 }
4340
4341 public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
4342 return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
4343
4344 @Override
4345 public boolean evaluate(ItemInfo info, View v, View parent) {
4346 return (info instanceof LauncherAppWidgetInfo) &&
4347 ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
4348 }
4349 });
4350 }
4351
4352 private View getFirstMatch(final ItemOperator operator) {
4353 final View[] value = new View[1];
4354 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4355 @Override
4356 public boolean evaluate(ItemInfo info, View v, View parent) {
4357 if (operator.evaluate(info, v, parent)) {
4358 value[0] = v;
4359 return true;
4360 }
4361 return false;
4362 }
4363 });
4364 return value[0];
4365 }
4366
4367 void clearDropTargets() {
4368 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4369 @Override
4370 public boolean evaluate(ItemInfo info, View v, View parent) {
4371 if (v instanceof DropTarget) {
4372 mDragController.removeDropTarget(((DropTarget) (v)));
4373 }
4374 // not done, process all the shortcuts
4375 return false;
4376 }
4377 });
4378 }
4379
4380 // Removes ALL items that match a given package name, this is usually called when a package
4381 // has been removed and we want to remove all components (widgets, shortcuts, apps) that
4382 // belong to that package.
4383 void removeItemsByPackageName(final ArrayList<String> packages, final UserHandleCompat user) {
4384 final HashSet<String> packageNames = new HashSet<String>();
4385 packageNames.addAll(packages);
4386 // Filter out all the ItemInfos that this is going to affect
4387 final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
4388 final HashSet<ComponentName> cns = new HashSet<ComponentName>();
4389 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4390 for (CellLayout layoutParent : cellLayouts) {
4391 ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4392 int childCount = layout.getChildCount();
4393 for (int i = 0; i < childCount; ++i) {
4394 View view = layout.getChildAt(i);
4395 infos.add(((ItemInfo) (view.getTag())));
4396 }
4397 }
4398 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4399 @Override
4400 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
4401 if (packageNames.contains(cn.getPackageName()) && info.user.equals(user)) {
4402 cns.add(cn);
4403 return true;
4404 }
4405 return false;
4406 }
4407 };
4408 LauncherModel.filterItemInfos(infos, filter);
4409 // Remove the affected components
4410 removeItemsByComponentName(cns, user);
4411 }
4412
4413 // Removes items that match the application info specified, when applications are removed
4414 // as a part of an update, this is called to ensure that other widgets and application
4415 // shortcuts are not removed.
4416 void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos, UserHandleCompat user) {
4417 // Just create a hash table of all the specific components that this will affect
4418 HashSet<ComponentName> cns = new HashSet<ComponentName>();
4419 for (AppInfo info : appInfos) {
4420 cns.add(info.componentName);
4421 }
4422 // Remove all the things
4423 removeItemsByComponentName(cns, user);
4424 }
4425
4426 void removeItemsByComponentName(final HashSet<ComponentName> componentNames, final UserHandleCompat u🔵
4427 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4428 for (final CellLayout layoutParent : cellLayouts) {
4429 final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4430 final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4431 for (int j = 0; j < layout.getChildCount(); j++) {
4432 final View view = layout.getChildAt(j);
4433 children.put(((ItemInfo) (view.getTag())), view);
4434 }
4435 final ArrayList<View> childrenToRemove = new ArrayList<View>();
4436 final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = new HashMap<FolderInf🔵
4437 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4438 @Override
4439 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
4440 if (parent instanceof FolderInfo) {
4441 if (componentNames.contains(cn) && info.user.equals(user)) {
4442 FolderInfo folder = ((FolderInfo) (parent));
4443 ArrayList<ShortcutInfo> appsToRemove;
4444 if (folderAppsToRemove.containsKey(folder)) {
4445 appsToRemove = folderAppsToRemove.get(folder);
4446 } else {
4447 appsToRemove = new ArrayList<ShortcutInfo>();
4448 folderAppsToRemove.put(folder, appsToRemove);
4449 }
4450 appsToRemove.add(((ShortcutInfo) (info)));
4451 return true;
4452 }
4453 } else if (componentNames.contains(cn) && info.user.equals(user)) {
4454 childrenToRemove.add(children.get(info));
4455 return true;
4456 }
4457 return false;
4458 }
4459 };
4460 LauncherModel.filterItemInfos(children.keySet(), filter);
4461 // Remove all the apps from their folders
4462 for (FolderInfo folder : folderAppsToRemove.keySet()) {
4463 ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4464 for (ShortcutInfo info : appsToRemove) {
4465 folder.remove(info);
4466 }
4467 }
4468 // Remove all the other children
4469 for (View child : childrenToRemove) {
4470 // Note: We can not remove the view directly from CellLayoutChildren as this
4471 // does not re-mark the spaces as unoccupied.
4472 layoutParent.removeViewInLayout(child);
4473 if (child instanceof DropTarget) {
4474 mDragController.removeDropTarget(((DropTarget) (child)));
4475 }
4476 }
4477 if (childrenToRemove.size() > 0) {
4478 layout.requestLayout();
4479 layout.invalidate();
4480 }
4481 }
4482 // Strip all the empty screens
4483 stripEmptyScreens();
4484 }
4485
4486 interface ItemOperator {
4487 /**
4488 * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}.
4489 *
4490 * @param info
4491 * info for the shortcut
4492 * @param view
4493 * view for the shortcut
4494 * @param parent
4495 * containing folder, or null
4496 * @return true if done, false to continue the map
4497 */
4498 public abstract boolean evaluate(ItemInfo info, View view, View parent);
4499 }
4500
4501 /**
4502 * Map the operator over the shortcuts and widgets, return the first-non-null value.
4503 *
4504 * @param recurse true: iterate over folder children. false: op get the folders themselves.
4505 * @param op the operator to map over the shortcuts
4506 */
4507 void mapOverItems(boolean recurse, ItemOperator op) {
4508 ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
4509 final int containerCount = containers.size();
4510 for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
4511 ShortcutAndWidgetContainer container = containers.get(containerIdx);
4512 // map over all the shortcuts on the workspace
4513 final int itemCount = container.getChildCount();
4514 for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
4515 View item = container.getChildAt(itemIdx);
4516 ItemInfo info = ((ItemInfo) (item.getTag()));
4517 if ((recurse && (info instanceof FolderInfo)) && (item instanceof FolderIcon)) {
4518 FolderIcon folder = ((FolderIcon) (item));
4519 ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
4520 // map over all the children in the folder
4521 final int childCount = folderChildren.size();
4522 for (int childIdx = 0; childIdx < childCount; childIdx++) {
4523 View child = folderChildren.get(childIdx);
4524 info = ((ItemInfo) (child.getTag()));
4525 if (op.evaluate(info, child, folder)) {
4526 return;
4527 }
4528 }
4529 } else if (op.evaluate(info, item, null)) {
4530 return;
4531 }
4532 }
4533 }
4534 }
4535
4536 void updateShortcutsAndWidgets(ArrayList<AppInfo> apps) {
4537 // Break the appinfo list per user
4538 final HashMap<UserHandleCompat, ArrayList<AppInfo>> appsPerUser = new HashMap<UserHandleCompat, A🔵
4539 for (AppInfo info : apps) {
4540 ArrayList<AppInfo> filtered = appsPerUser.get(info.user);
4541 if (filtered == null) {
4542 filtered = new ArrayList<AppInfo>();
4543 appsPerUser.put(info.user, filtered);
4544 }
4545 filtered.add(info);
4546 }
4547 for (Map.Entry<UserHandleCompat, ArrayList<AppInfo>> entry : appsPerUser.entrySet()) {
4548 updateShortcutsAndWidgetsPerUser(entry.getValue(), entry.getKey());
4549 }
4550 }
4551
4552 private void updateShortcutsAndWidgetsPerUser(ArrayList<AppInfo> apps, final UserHandleCompat user) {
4553 // Create a map of the apps to test against
4554 final HashMap<ComponentName, AppInfo> appsMap = new HashMap<ComponentName, AppInfo>();
4555 final HashSet<String> pkgNames = new HashSet<String>();
4556 for (AppInfo ai : apps) {
4557 appsMap.put(ai.componentName, ai);
4558 pkgNames.add(ai.componentName.getPackageName());
4559 }
4560 final HashSet<ComponentName> iconsToRemove = new HashSet<ComponentName>();
4561 mapOverItems(MAP_RECURSE, new ItemOperator() {
4562 @Override
4563 public boolean evaluate(ItemInfo info, View v, View parent) {
4564 if ((info instanceof ShortcutInfo) && (v instanceof BubbleTextView)) {
4565 ShortcutInfo shortcutInfo = ((ShortcutInfo) (info));
4566 ComponentName cn = shortcutInfo.getTargetComponent();
4567 AppInfo appInfo = appsMap.get(cn);
4568 if (((user.equals(shortcutInfo.user) && (cn != null)) && LauncherModel.isShortcutInfo🔵
4569 boolean promiseStateChanged = false;
4570 boolean infoUpdated = false;
4571 if (shortcutInfo.isPromise()) {
4572 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
4573 // Auto install icon
4574 PackageManager pm = getContext().getPackageManager();
4575 ResolveInfo matched = pm.resolveActivity(new Intent(Intent.ACTION_MAIN).s🔵
4576 if (matched == null) {
4577 // Try to find the best match activity.
4578 Intent intent = pm.getLaunchIntentForPackage(cn.getPackageName());
4579 if (intent != null) {
4580 cn = intent.getComponent();
4581 appInfo = appsMap.get(cn);
4582 }
4583 if ((intent == null) || (appsMap == null)) {
4584 // Could not find a default activity. Remove this item.
4585 iconsToRemove.add(shortcutInfo.getTargetComponent());
4586 // process next shortcut.
4587 return false;
4588 }
4589 shortcutInfo.promisedIntent = intent;
4590 }
4591 }
4592 // Restore the shortcut.
4593 shortcutInfo.intent = shortcutInfo.promisedIntent;
4594 shortcutInfo.promisedIntent = null;
4595 shortcutInfo.status &= ((~ShortcutInfo.FLAG_RESTORED_ICON) & (~ShortcutInfo.F🔵
4596 promiseStateChanged = true;
4597 infoUpdated = true;
4598 shortcutInfo.updateIcon(mIconCache);
4599 LauncherModel.updateItemInDatabase(getContext(), shortcutInfo);
4600 }
4601 if (appInfo != null) {
4602 shortcutInfo.updateIcon(mIconCache);
4603 shortcutInfo.title = appInfo.title.toString();
4604 shortcutInfo.contentDescription = appInfo.contentDescription;
4605 infoUpdated = true;
4606 }
4607 if (infoUpdated) {
4608 BubbleTextView shortcut = ((BubbleTextView) (v));
4609 shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, promiseStateCh🔵
4610 if (parent != null) {
4611 parent.invalidate();
4612 }
4613 }
4614 }
4615 }
4616 // process all the shortcuts
4617 return false;
4618 }
4619 });
4620 if (!iconsToRemove.isEmpty()) {
4621 removeItemsByComponentName(iconsToRemove, user);
4622 }
4623 if (user.equals(UserHandleCompat.myUserHandle())) {
4624 restorePendingWidgets(pkgNames);
4625 }
4626 }
4627
4628 public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
4629 ArrayList<String> packages = new ArrayList<String>(1);
4630 packages.add(packageName);
4631 LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
4632 removeItemsByPackageName(packages, user);
4633 }
4634
4635 public void updatePackageBadge(final String packageName, final UserHandleCompat user) {
4636 mapOverItems(MAP_RECURSE, new ItemOperator() {
4637 @Override
4638 public boolean evaluate(ItemInfo info, View v, View parent) {
4639 if ((info instanceof ShortcutInfo) && (v instanceof BubbleTextView)) {
4640 ShortcutInfo shortcutInfo = ((ShortcutInfo) (info));
4641 ComponentName cn = shortcutInfo.getTargetComponent();
4642 if (((user.equals(shortcutInfo.user) && (cn != null)) && shortcutInfo.isPromise()) &&🔵
4643 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
4644 // For auto install apps update the icon as well as label.
4645 mIconCache.getTitleAndIcon(shortcutInfo, shortcutInfo.promisedIntent, user, t🔵
4646 } else {
4647 // Only update the icon for restored apps.
4648 shortcutInfo.updateIcon(mIconCache);
4649 }
4650 BubbleTextView shortcut = ((BubbleTextView) (v));
4651 shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, false);
4652 if (parent != null) {
4653 parent.invalidate();
4654 }
4655 }
4656 }
4657 // process all the shortcuts
4658 return false;
4659 }
4660 });
4661 }
4662
4663 public void updatePackageState(ArrayList<PackageInstallInfo> installInfos) {
4664 HashSet<String> completedPackages = new HashSet<String>();
4665 for (final PackageInstallInfo installInfo : installInfos) {
4666 mapOverItems(MAP_RECURSE, new ItemOperator() {
4667 @Override
4668 public boolean evaluate(ItemInfo info, View v, View parent) {
4669 if ((info instanceof ShortcutInfo) && (v instanceof BubbleTextView)) {
4670 ShortcutInfo si = ((ShortcutInfo) (info));
4671 ComponentName cn = si.getTargetComponent();
4672 if ((si.isPromise() && (cn != null)) && installInfo.packageName.equals(cn.getPack🔵
4673 si.setInstallProgress(installInfo.progress);
4674 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
4675 // Mark this info as broken.
4676 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
4677 }
4678 ((BubbleTextView) (v)).applyState(false);
4679 }
4680 } else if (((v instanceof PendingAppWidgetHostView) && (info instanceof LauncherAppWi🔵
4681 ((LauncherAppWidgetInfo) (info)).installProgress = installInfo.progress;
4682 ((PendingAppWidgetHostView) (v)).applyState();
4683 }
4684 // process all the shortcuts
4685 return false;
4686 }
4687 });
4688 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
4689 completedPackages.add(installInfo.packageName);
4690 }
4691 }
4692 // Note that package states are sent only for myUser
4693 if (!completedPackages.isEmpty()) {
4694 restorePendingWidgets(completedPackages);
4695 }
4696 }
4697
4698 private void restorePendingWidgets(final Set<String> installedPackaged) {
4699 final ArrayList<LauncherAppWidgetInfo> changedInfo = new ArrayList<LauncherAppWidgetInfo>();
4700 // Iterate non recursively as widgets can't be inside a folder.
4701 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4702 @Override
4703 public boolean evaluate(ItemInfo info, View v, View parent) {
4704 if (info instanceof LauncherAppWidgetInfo) {
4705 LauncherAppWidgetInfo widgetInfo = ((LauncherAppWidgetInfo) (info));
4706 if (widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) && insta🔵
4707 changedInfo.add(widgetInfo);
4708 // Remove the provider not ready flag
4709 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
4710 LauncherModel.updateItemInDatabase(getContext(), widgetInfo);
4711 }
4712 }
4713 // process all the widget
4714 return false;
4715 }
4716 });
4717 if (!changedInfo.isEmpty()) {
4718 DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo, mLauncher.getApp🔵
4719 if (LauncherModel.findAppWidgetProviderInfoWithComponent(getContext(), changedInfo.get(0).pro🔵
4720 // Re-inflate the widgets which have changed status
4721 widgetRefresh.run();
4722 } else {
4723 // widgetRefresh will automatically run when the packages are updated.
4724 }
4725 }
4726 }
4727
4728 private void moveToScreen(int page, boolean animate) {
4729 if (!workspaceInModalState()) {
4730 if (animate) {
4731 snapToPage(page);
4732 } else {
4733 setCurrentPage(page);
4734 }
4735 }
4736 View child = getChildAt(page);
4737 if (child != null) {
4738 child.requestFocus();
4739 }
4740 }
4741
4742 void moveToDefaultScreen(boolean animate) {
4743 moveToScreen(mDefaultPage, animate);
4744 }
4745
4746 void moveToCustomContentScreen(boolean animate) {
4747 if (hasCustomContent()) {
4748 int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
4749 if (animate) {
4750 snapToPage(ccIndex);
4751 } else {
4752 setCurrentPage(ccIndex);
4753 }
4754 View child = getChildAt(ccIndex);
4755 if (child != null) {
4756 child.requestFocus();
4757 }
4758 }
4759 exitWidgetResizeMode();
4760 }
4761
4762 @Override
4763 protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
4764 long screenId = getScreenIdForPageIndex(pageIndex);
4765 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
4766 int count = mScreenOrder.size() - numCustomPages();
4767 if (count > 1) {
4768 return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
4769 R.drawable.ic_pageindicator_add);
4770 }
4771 }
4772
4773 return super.getPageIndicatorMarker(pageIndex);
4774 }
4775
4776 @Override
4777 public void syncPages() {
4778 }
4779
4780 @Override
4781 public void syncPageItems(int page, boolean immediate) {
4782 }
4783
4784 protected String getPageIndicatorDescription() {
4785 String settings = getResources().getString(R.string.settings_button_text);
4786 return getCurrentPageDescription() + ", " + settings;
4787 }
4788
4789 protected String getCurrentPageDescription() {
4790 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
4791 int delta = numCustomPages();
4792 if (hasCustomContent() && getNextPage() == 0) {
4793 return mCustomContentDescription;
4794 }
4795 return String.format(getContext().getString(R.string.workspace_scroll_format),
4796 page + 1 - delta, getChildCount() - delta);
4797 }
4798
4799 public void getLocationInDragLayer(int[] loc) {
4800 mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
4801 }
4802
4803 /**
4804 * Used as a workaround to ensure that the AppWidgetService receives the
4805 * PACKAGE_ADDED broadcast before updating widgets.
4806 */
4807 private class DeferredWidgetRefresh implements Runnable {
4808 private final ArrayList<LauncherAppWidgetInfo> mInfos;
4809
4810 private final LauncherAppWidgetHost mHost;
4811
4812 private final Handler mHandler;
4813
4814 private boolean mRefreshPending;
4815
4816 public DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos, LauncherAppWidgetHost host) 🔵
4817 mInfos = infos;
4818 mHost = host;
4819 mHandler = new Handler();
4820 mRefreshPending = true;
4821 mHost.addProviderChangeListener(this);
4822 // Force refresh after 10 seconds, if we don't get the provider changed event.
4823 // This could happen when the provider is no longer available in the app.
4824 mHandler.postDelayed(this, 10000);
4825 }
4826
4827 @Override
4828 public void run() {
4829 mHost.removeProviderChangeListener(this);
4830 mHandler.removeCallbacks(this);
4831
4832 if (!mRefreshPending) {
4833 return;
4834 }
4835
4836 mRefreshPending = false;
4837
4838 for (LauncherAppWidgetInfo info : mInfos) {
4839 if (info.hostView instanceof PendingAppWidgetHostView) {
4840 PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView;
4841 mLauncher.removeAppWidget(info);
4842
4843 CellLayout cl = (CellLayout) view.getParent().getParent();
4844 // Remove the current widget
4845 cl.removeView(view);
4846 mLauncher.bindAppWidget(info);
4847 }
4848 }
4849 }
4850 }
4851 }
|